[gtksourceview] Refactor undo manager in public interface



commit 945390f92251da27455f8c79bd7263875cbe333a
Author: Jesse van den Kieboom <jessevdk gnome org>
Date:   Sat Feb 20 15:26:07 2010 +0100

    Refactor undo manager in public interface
    
    This adds the GtkSourceUndoManager public interface which can be
    implemented to override the default undo manager on a buffer. The
    default undo manager is renamed to GtkSourceUndoManagerDefault
    and will be used unless a different undo manager is set using
    gtk_source_buffer_set_undo_manager.

 gtksourceview/Makefile.am                   |    4 +-
 gtksourceview/gtksourcebuffer.c             |  181 ++++-
 gtksourceview/gtksourcebuffer.h             |    5 +
 gtksourceview/gtksourceundomanager.c        | 1199 ++++-----------------------
 gtksourceview/gtksourceundomanager.h        |   65 +-
 gtksourceview/gtksourceundomanagerdefault.c | 1194 ++++++++++++++++++++++++++
 gtksourceview/gtksourceundomanagerdefault.h |   64 ++
 7 files changed, 1625 insertions(+), 1087 deletions(-)
---
diff --git a/gtksourceview/Makefile.am b/gtksourceview/Makefile.am
index 20d5f8c..563a8cf 100644
--- a/gtksourceview/Makefile.am
+++ b/gtksourceview/Makefile.am
@@ -34,6 +34,7 @@ libgtksourceview_headers =			\
 	gtksourcemark.h				\
 	gtksourceprintcompositor.h		\
 	gtksourcegutter.h			\
+	gtksourceundomanager.h			\
 	gtksourcecompletion.h			\
 	gtksourcecompletioninfo.h		\
 	gtksourcecompletionitem.h		\
@@ -51,7 +52,7 @@ NOINST_H_FILES = \
 	gtksourcegutter-private.h	\
 	gtksourcelanguage-private.h	\
 	gtksourcestyle-private.h	\
-	gtksourceundomanager.h		\
+	gtksourceundomanagerdefault.h	\
 	gtksourceview-i18n.h		\
 	gtksourceview-utils.h		\
 	gtktextregion.h
@@ -61,6 +62,7 @@ libgtksourceview_2_0_la_SOURCES = 	\
 	gtksourceiter.c			\
 	gtksourceview.c 		\
 	gtksourceundomanager.c 		\
+	gtksourceundomanagerdefault.c	\
 	gtktextregion.c 		\
 	gtksourcelanguage.c 		\
 	gtksourcelanguagemanager.c 	\
diff --git a/gtksourceview/gtksourcebuffer.c b/gtksourceview/gtksourcebuffer.c
index 82c03a8..edbcab8 100644
--- a/gtksourceview/gtksourcebuffer.c
+++ b/gtksourceview/gtksourcebuffer.c
@@ -39,6 +39,7 @@
 #include "gtksourceiter.h"
 #include "gtksourcestyleschememanager.h"
 #include "gtksourcestyle-private.h"
+#include "gtksourceundomanagerdefault.h"
 
 /**
  * SECTION:buffer
@@ -100,7 +101,8 @@ enum {
 	PROP_HIGHLIGHT_MATCHING_BRACKETS,
 	PROP_MAX_UNDO_LEVELS,
 	PROP_LANGUAGE,
-	PROP_STYLE_SCHEME
+	PROP_STYLE_SCHEME,
+	PROP_UNDO_MANAGER
 };
 
 struct _GtkSourceBufferPrivate
@@ -122,6 +124,7 @@ struct _GtkSourceBufferPrivate
 	GtkSourceStyleScheme  *style_scheme;
 
 	GtkSourceUndoManager  *undo_manager;
+	gint                   max_undo_levels;
 };
 
 G_DEFINE_TYPE (GtkSourceBuffer, gtk_source_buffer, GTK_TYPE_TEXT_BUFFER)
@@ -141,11 +144,9 @@ static void      gtk_source_buffer_get_property         (GObject
 							 guint                    prop_id,
 							 GValue                  *value,
 							 GParamSpec              *pspec);
-static void 	 gtk_source_buffer_can_undo_handler 	(GtkSourceUndoManager    *um,
-							 gboolean                 can_undo,
+static void 	 gtk_source_buffer_can_undo_handler 	(GtkSourceUndoManager    *manager,
 							 GtkSourceBuffer         *buffer);
-static void 	 gtk_source_buffer_can_redo_handler	(GtkSourceUndoManager    *um,
-							 gboolean                 can_redo,
+static void 	 gtk_source_buffer_can_redo_handler	(GtkSourceUndoManager    *manager,
 							 GtkSourceBuffer         *buffer);
 static void 	 gtk_source_buffer_real_insert_text 	(GtkTextBuffer           *buffer,
 							 GtkTextIter             *iter,
@@ -229,7 +230,8 @@ gtk_source_buffer_class_init (GtkSourceBufferClass *klass)
 	/**
 	 * GtkSourceBuffer:max-undo-levels:
 	 *
-	 * Number of undo levels for the buffer. -1 means no limit.
+	 * Number of undo levels for the buffer. -1 means no limit. This property
+	 * will only affect the default undo manager.
 	 */
 	g_object_class_install_property (object_class,
 					 PROP_MAX_UNDO_LEVELS,
@@ -284,6 +286,14 @@ gtk_source_buffer_class_init (GtkSourceBufferClass *klass)
 							      GTK_TYPE_SOURCE_STYLE_SCHEME,
 							      G_PARAM_READWRITE));
 
+	g_object_class_install_property (object_class,
+	                                 PROP_UNDO_MANAGER,
+	                                 g_param_spec_object ("undo-manager",
+	                                                      _("Undo manager"),
+	                                                      _("The buffer undo manager"),
+	                                                      GTK_TYPE_SOURCE_UNDO_MANAGER,
+	                                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
 	param_types[0] = GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE;
 	param_types[1] = GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE;
 
@@ -337,6 +347,49 @@ gtk_source_buffer_class_init (GtkSourceBufferClass *klass)
 }
 
 static void
+set_undo_manager (GtkSourceBuffer      *buffer,
+                  GtkSourceUndoManager *manager)
+{
+	if (manager == buffer->priv->undo_manager)
+	{
+		return;
+	}
+
+	if (buffer->priv->undo_manager != NULL)
+	{
+		g_signal_handlers_disconnect_by_func (buffer->priv->undo_manager,
+		                                      G_CALLBACK (gtk_source_buffer_can_undo_handler),
+		                                      buffer);
+
+		g_signal_handlers_disconnect_by_func (buffer->priv->undo_manager,
+		                                      G_CALLBACK (gtk_source_buffer_can_redo_handler),
+		                                      buffer);
+
+		g_object_unref (buffer->priv->undo_manager);
+		buffer->priv->undo_manager = NULL;
+	}
+
+	if (manager != NULL)
+	{
+		buffer->priv->undo_manager = g_object_ref (manager);
+
+		g_signal_connect (buffer->priv->undo_manager,
+		                  "can-undo-changed",
+		                  G_CALLBACK (gtk_source_buffer_can_undo_handler),
+		                  buffer);
+
+		g_signal_connect (buffer->priv->undo_manager,
+		                  "can-redo-changed",
+		                  G_CALLBACK (gtk_source_buffer_can_redo_handler),
+		                  buffer);
+
+		/* Notify possible changes in the can-undo/redo state */
+		g_object_notify (G_OBJECT (buffer), "can-undo");
+		g_object_notify (G_OBJECT (buffer), "can-redo");
+	}
+}
+
+static void
 gtk_source_buffer_init (GtkSourceBuffer *buffer)
 {
 	GtkSourceBufferPrivate *priv;
@@ -346,27 +399,16 @@ gtk_source_buffer_init (GtkSourceBuffer *buffer)
 
 	buffer->priv = priv;
 
-	priv->undo_manager = gtk_source_undo_manager_new (GTK_TEXT_BUFFER (buffer));
-
 	priv->highlight_syntax = TRUE;
 	priv->highlight_brackets = TRUE;
 	priv->bracket_mark = NULL;
 	priv->bracket_found = FALSE;
 
 	priv->source_marks = g_array_new (FALSE, FALSE, sizeof (GtkSourceMark *));
-
 	priv->style_scheme = _gtk_source_style_scheme_get_default ();
+
 	if (priv->style_scheme != NULL)
 		g_object_ref (priv->style_scheme);
-
-	g_signal_connect (priv->undo_manager,
-			  "can_undo",
-			  G_CALLBACK (gtk_source_buffer_can_undo_handler),
-			  buffer);
-	g_signal_connect (priv->undo_manager,
-			  "can_redo",
-			  G_CALLBACK (gtk_source_buffer_can_redo_handler),
-			  buffer);
 }
 
 static GObject *
@@ -375,13 +417,21 @@ gtk_source_buffer_constructor (GType                  type,
 			       GObjectConstructParam *construct_param)
 {
 	GObject *object;
+	GtkSourceBuffer *buffer;
 
 	object = G_OBJECT_CLASS(gtk_source_buffer_parent_class)->constructor (type,
 									      n_construct_properties,
 									      construct_param);
 
 	/* we need to know that the tag-table was set */
-	GTK_SOURCE_BUFFER (object)->priv->constructed = TRUE;
+	buffer = GTK_SOURCE_BUFFER (object);
+	buffer->priv->constructed = TRUE;
+
+	if (buffer->priv->undo_manager == NULL)
+	{
+		/* This will install the default undo manager */
+		gtk_source_buffer_set_undo_manager (buffer, NULL);
+	}
 
 	return object;
 }
@@ -416,8 +466,7 @@ gtk_source_buffer_dispose (GObject *object)
 
 	if (buffer->priv->undo_manager != NULL)
 	{
-		g_object_unref (buffer->priv->undo_manager);
-		buffer->priv->undo_manager = NULL;
+		set_undo_manager (buffer, NULL);
 	}
 
 	if (buffer->priv->highlight_engine != NULL)
@@ -481,6 +530,11 @@ gtk_source_buffer_set_property (GObject      *object,
 							    g_value_get_object (value));
 			break;
 
+		case PROP_UNDO_MANAGER:
+			gtk_source_buffer_set_undo_manager (source_buffer,
+			                                    g_value_get_object (value));
+			break;
+
 		default:
 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 			break;
@@ -513,7 +567,7 @@ gtk_source_buffer_get_property (GObject    *object,
 
 		case PROP_MAX_UNDO_LEVELS:
 			g_value_set_int (value,
-					 gtk_source_buffer_get_max_undo_levels (source_buffer));
+					 source_buffer->priv->max_undo_levels);
 			break;
 
 		case PROP_LANGUAGE:
@@ -532,6 +586,10 @@ gtk_source_buffer_get_property (GObject    *object,
 			g_value_set_boolean (value, gtk_source_buffer_can_redo (source_buffer));
 			break;
 
+		case PROP_UNDO_MANAGER:
+			g_value_set_object (value, source_buffer->priv->undo_manager);
+			break;
+
 		default:
 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 			break;
@@ -580,9 +638,8 @@ gtk_source_buffer_new_with_language (GtkSourceLanguage *language)
 }
 
 static void
-gtk_source_buffer_can_undo_handler (GtkSourceUndoManager  	*um,
-				    gboolean			 can_undo,
-				    GtkSourceBuffer 		*buffer)
+gtk_source_buffer_can_undo_handler (GtkSourceUndoManager *manager,
+                                    GtkSourceBuffer      *buffer)
 {
 	g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));
 
@@ -590,9 +647,8 @@ gtk_source_buffer_can_undo_handler (GtkSourceUndoManager  	*um,
 }
 
 static void
-gtk_source_buffer_can_redo_handler (GtkSourceUndoManager  	*um,
-				    gboolean         		 can_redo,
-				    GtkSourceBuffer 		*buffer)
+gtk_source_buffer_can_redo_handler (GtkSourceUndoManager *manager,
+                                    GtkSourceBuffer      *buffer)
 {
 	g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));
 
@@ -1106,7 +1162,7 @@ gtk_source_buffer_get_max_undo_levels (GtkSourceBuffer *buffer)
 {
 	g_return_val_if_fail (GTK_IS_SOURCE_BUFFER (buffer), 0);
 
-	return gtk_source_undo_manager_get_max_undo_levels (buffer->priv->undo_manager);
+	return buffer->priv->max_undo_levels;
 }
 
 /**
@@ -1133,13 +1189,20 @@ gtk_source_buffer_set_max_undo_levels (GtkSourceBuffer *buffer,
 {
 	g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));
 
-	if (gtk_source_undo_manager_get_max_undo_levels (
-		    buffer->priv->undo_manager) != max_undo_levels)
+	if (buffer->priv->max_undo_levels == max_undo_levels)
 	{
-		gtk_source_undo_manager_set_max_undo_levels (buffer->priv->undo_manager,
-							     max_undo_levels);
-		g_object_notify (G_OBJECT (buffer), "max-undo-levels");
+		return;
 	}
+
+	buffer->priv->max_undo_levels = max_undo_levels;
+
+	if (GTK_IS_SOURCE_UNDO_MANAGER_DEFAULT (buffer->priv->undo_manager))
+	{
+		gtk_source_undo_manager_default_set_max_undo_levels (GTK_SOURCE_UNDO_MANAGER_DEFAULT (buffer->priv->undo_manager),
+		                                                     max_undo_levels);
+	}
+
+	g_object_notify (G_OBJECT (buffer), "max-undo-levels");
 }
 
 /**
@@ -2194,3 +2257,53 @@ gtk_source_buffer_iter_backward_to_context_class_toggle (GtkSourceBuffer *buffer
 	}
 }
 
+/**
+ * gtk_source_buffer_set_undo_manager:
+ * @buffer: A #GtkSourceBuffer
+ * @manager: A #GtkSourceUndoManager
+ *
+ * Set the buffer undo manager. If @manager is %NULL the default undo manager
+ * will be set.
+ *
+ **/
+void
+gtk_source_buffer_set_undo_manager (GtkSourceBuffer      *buffer,
+                                    GtkSourceUndoManager *manager)
+{
+	g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));
+	g_return_if_fail (manager == NULL || GTK_IS_SOURCE_UNDO_MANAGER (manager));
+
+	if (manager == NULL)
+	{
+		manager = g_object_new (GTK_TYPE_SOURCE_UNDO_MANAGER_DEFAULT,
+		                        "buffer", buffer,
+		                        "max-undo-levels", buffer->priv->max_undo_levels,
+		                        NULL);
+	}
+	else
+	{
+		g_object_ref (manager);
+	}
+
+	set_undo_manager (buffer, manager);
+	g_object_unref (manager);
+
+	g_object_notify (G_OBJECT (buffer), "undo-manager");
+}
+
+/**
+ * gtk_source_buffer_get_undo_manager:
+ * @buffer: A #GtkSourceBuffer
+ *
+ * Get the undo manager associated with the buffer.
+ *
+ * Returns: A #GtkSourceUndoManager
+ *
+ **/
+GtkSourceUndoManager *
+gtk_source_buffer_get_undo_manager (GtkSourceBuffer *buffer)
+{
+	g_return_val_if_fail (GTK_IS_SOURCE_BUFFER (buffer), NULL);
+
+	return buffer->priv->undo_manager;
+}
diff --git a/gtksourceview/gtksourcebuffer.h b/gtksourceview/gtksourcebuffer.h
index d2801a6..8d62949 100644
--- a/gtksourceview/gtksourcebuffer.h
+++ b/gtksourceview/gtksourcebuffer.h
@@ -30,6 +30,7 @@
 #include <gtksourceview/gtksourcelanguage.h>
 #include <gtksourceview/gtksourcemark.h>
 #include <gtksourceview/gtksourcestylescheme.h>
+#include <gtksourceview/gtksourceundomanager.h>
 
 G_BEGIN_DECLS
 
@@ -156,6 +157,10 @@ gboolean		 gtk_source_buffer_iter_backward_to_context_class_toggle
 								 GtkTextIter		*iter,
 								 const gchar		*context_class);
 
+GtkSourceUndoManager	*gtk_source_buffer_get_undo_manager	(GtkSourceBuffer	*buffer);
+void			 gtk_source_buffer_set_undo_manager	(GtkSourceBuffer	*buffer,
+								 GtkSourceUndoManager	*manager);
+
 /* private */
 void			 _gtk_source_buffer_update_highlight	(GtkSourceBuffer        *buffer,
 								 const GtkTextIter      *start,
diff --git a/gtksourceview/gtksourceundomanager.c b/gtksourceview/gtksourceundomanager.c
index 4a29f87..1c16e8a 100644
--- a/gtksourceview/gtksourceundomanager.c
+++ b/gtksourceview/gtksourceundomanager.c
@@ -33,1094 +33,255 @@
 #include "gtksourceundomanager.h"
 #include "gtksourceview-marshal.h"
 
-
-#define DEFAULT_MAX_UNDO_LEVELS		-1
-
-/*
- * The old code which used a GSList and g_slist_nth_element
- * was way too slow for many operations (search/replace, hit Ctrl-Z,
- * see gedit freezes). GSList was replaced with a GPtrArray with
- * minimal changes to the code: to avoid insertions at the beginning
- * of the array it uses array beginning as the list end and vice versa;
- * hence bunch of ugly action_list_* functions.
- * FIXME: so, somebody please rewrite this stuff using some nice data
- * structures or something.
- */
-
-typedef struct _GtkSourceUndoAction  			GtkSourceUndoAction;
-typedef struct _GtkSourceUndoInsertAction		GtkSourceUndoInsertAction;
-typedef struct _GtkSourceUndoDeleteAction		GtkSourceUndoDeleteAction;
-
-typedef enum {
-	GTK_SOURCE_UNDO_ACTION_INSERT,
-	GTK_SOURCE_UNDO_ACTION_DELETE
-} GtkSourceUndoActionType;
-
-/*
- * We use offsets instead of GtkTextIters because the last ones
- * require to much memory in this context without giving us any advantage.
- */
-
-struct _GtkSourceUndoInsertAction
-{
-	gint   pos;
-	gchar *text;
-	gint   length;
-	gint   chars;
-};
-
-struct _GtkSourceUndoDeleteAction
-{
-	gint   start;
-	gint   end;
-	gchar *text;
-	gboolean forward;
-};
-
-struct _GtkSourceUndoAction
-{
-	GtkSourceUndoActionType action_type;
-
-	union {
-		GtkSourceUndoInsertAction  insert;
-		GtkSourceUndoDeleteAction  delete;
-	} action;
-
-	gint order_in_group;
-
-	/* It is TRUE whether the action can be merged with the following action. */
-	guint mergeable : 1;
-
-	/* It is TRUE whether the action is marked as "modified".
-	 * An action is marked as "modified" if it changed the
-	 * state of the buffer from "not modified" to "modified". Only the first
-	 * action of a group can be marked as modified.
-	 * There can be a single action marked as "modified" in the actions list.
-	 */
-	guint modified  : 1;
-};
-
-/* INVALID is a pointer to an invalid action */
-#define INVALID ((void *) "IA")
-
-struct _GtkSourceUndoManagerPrivate
+/* Signals */
+enum
 {
-	GtkTextBuffer	*document;
-
-	GPtrArray	*actions;
-	gint 		 next_redo;
-
-	gint 		 actions_in_current_group;
-
-	gint		 running_not_undoable_actions;
-
-	gint		 num_of_groups;
-
-	gint		 max_undo_levels;
-
-	guint	 	 can_undo : 1;
-	guint		 can_redo : 1;
-
-	/* It is TRUE whether, while undoing an action of the current group (with order_in_group > 1),
-	 * the state of the buffer changed from "not modified" to "modified".
-	 */
-	guint	 	 modified_undoing_group : 1;
-
-	/* Pointer to the action (in the action list) marked as "modified".
-	 * It is NULL when no action is marked as "modified".
-	 * It is INVALID when the action marked as "modified" has been removed
-	 * from the action list (freeing the list or resizing it) */
-	GtkSourceUndoAction *modified_action;
-};
-
-enum {
-	CAN_UNDO,
-	CAN_REDO,
-	LAST_SIGNAL
+	CAN_UNDO_CHANGED,
+	CAN_REDO_CHANGED,
+	NUM_SIGNALS
 };
 
-G_DEFINE_TYPE (GtkSourceUndoManager, gtk_source_undo_manager, G_TYPE_OBJECT)
-
-static void gtk_source_undo_manager_finalize 			(GObject 			*object);
-
-static void gtk_source_undo_manager_insert_text_handler 	(GtkTextBuffer 			*buffer,
-							 	 GtkTextIter 			*pos,
-		                             		 	 const 	gchar 			*text,
-							 	 gint 				 length,
-							 	 GtkSourceUndoManager 		*um);
-static void gtk_source_undo_manager_delete_range_handler 	(GtkTextBuffer 			*buffer,
-							 	 GtkTextIter 			*start,
-                        		      		 	 GtkTextIter 			*end,
-							 	 GtkSourceUndoManager 		*um);
-static void gtk_source_undo_manager_begin_user_action_handler 	(GtkTextBuffer 			*buffer,
-								 GtkSourceUndoManager 		*um);
-static void gtk_source_undo_manager_modified_changed_handler	(GtkTextBuffer                  *buffer,
-								 GtkSourceUndoManager           *um);
-
-static void gtk_source_undo_manager_free_action_list 		(GtkSourceUndoManager 		*um);
-
-static void gtk_source_undo_manager_add_action 			(GtkSourceUndoManager 		*um,
-		                                         	 const GtkSourceUndoAction 	*undo_action);
-static void gtk_source_undo_manager_free_first_n_actions 	(GtkSourceUndoManager 		*um,
-								 gint 				 n);
-static void gtk_source_undo_manager_check_list_size 		(GtkSourceUndoManager 		*um);
-
-static gboolean gtk_source_undo_manager_merge_action 		(GtkSourceUndoManager 		*um,
-		                                        	 const GtkSourceUndoAction 	*undo_action);
-
-static guint 		undo_manager_signals [LAST_SIGNAL] 	= { 0 };
-
-static void
-gtk_source_undo_manager_class_init (GtkSourceUndoManagerClass *klass)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-  	object_class->finalize = gtk_source_undo_manager_finalize;
-
-        klass->can_undo 	= NULL;
-	klass->can_redo 	= NULL;
-
-	undo_manager_signals[CAN_UNDO] =
-   		g_signal_new ("can_undo",
-			      G_OBJECT_CLASS_TYPE (object_class),
-			      G_SIGNAL_RUN_LAST,
-			      G_STRUCT_OFFSET (GtkSourceUndoManagerClass, can_undo),
-			      NULL, NULL,
-			      _gtksourceview_marshal_VOID__BOOLEAN,
-			      G_TYPE_NONE,
-			      1,
-			      G_TYPE_BOOLEAN);
-
-	undo_manager_signals[CAN_REDO] =
-   		g_signal_new ("can_redo",
-			      G_OBJECT_CLASS_TYPE (object_class),
-			      G_SIGNAL_RUN_LAST,
-			      G_STRUCT_OFFSET (GtkSourceUndoManagerClass, can_redo),
-			      NULL, NULL,
-			      _gtksourceview_marshal_VOID__BOOLEAN,
-			      G_TYPE_NONE,
-			      1,
-			      G_TYPE_BOOLEAN);
-
-	g_type_class_add_private (object_class, sizeof(GtkSourceUndoManagerPrivate));
-}
-
-static void
-gtk_source_undo_manager_init (GtkSourceUndoManager *um)
-{
-	um->priv = G_TYPE_INSTANCE_GET_PRIVATE (um, GTK_TYPE_SOURCE_UNDO_MANAGER,
-						GtkSourceUndoManagerPrivate);
-
-	um->priv->actions = g_ptr_array_new ();
-	um->priv->next_redo = 0;
-
-	um->priv->can_undo = FALSE;
-	um->priv->can_redo = FALSE;
-
-	um->priv->running_not_undoable_actions = 0;
-
-	um->priv->num_of_groups = 0;
-
-	um->priv->max_undo_levels = DEFAULT_MAX_UNDO_LEVELS;
-
-	um->priv->modified_action = NULL;
-
-	um->priv->modified_undoing_group = FALSE;
-}
-
-static void
-gtk_source_undo_manager_finalize (GObject *object)
-{
-	GtkSourceUndoManager *um;
-
-	g_return_if_fail (object != NULL);
-	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (object));
-
-   	um = GTK_SOURCE_UNDO_MANAGER (object);
-
-	g_return_if_fail (um->priv != NULL);
-
-	gtk_source_undo_manager_free_action_list (um);
-	g_ptr_array_free (um->priv->actions, TRUE);
-
-	g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document),
-			  G_CALLBACK (gtk_source_undo_manager_delete_range_handler),
-			  um);
-
-	g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document),
-			  G_CALLBACK (gtk_source_undo_manager_insert_text_handler),
-			  um);
-
-	g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document),
-			  G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler),
-			  um);
+static guint signals[NUM_SIGNALS] = {0,};
 
-	G_OBJECT_CLASS (gtk_source_undo_manager_parent_class)->finalize (object);
-}
-
-GtkSourceUndoManager*
-gtk_source_undo_manager_new (GtkTextBuffer* buffer)
-{
- 	GtkSourceUndoManager *um;
-
-	um = GTK_SOURCE_UNDO_MANAGER (g_object_new (GTK_TYPE_SOURCE_UNDO_MANAGER, NULL));
-
-	g_return_val_if_fail (um->priv != NULL, NULL);
-  	um->priv->document = buffer;
-
-	g_signal_connect (G_OBJECT (buffer), "insert_text",
-			  G_CALLBACK (gtk_source_undo_manager_insert_text_handler),
-			  um);
-
-	g_signal_connect (G_OBJECT (buffer), "delete_range",
-			  G_CALLBACK (gtk_source_undo_manager_delete_range_handler),
-			  um);
-
-	g_signal_connect (G_OBJECT (buffer), "begin_user_action",
-			  G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler),
-			  um);
-
-	g_signal_connect (G_OBJECT (buffer), "modified_changed",
-			  G_CALLBACK (gtk_source_undo_manager_modified_changed_handler),
-			  um);
-	return um;
-}
-
-void
-gtk_source_undo_manager_begin_not_undoable_action (GtkSourceUndoManager *um)
-{
-	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
-	g_return_if_fail (um->priv != NULL);
-
-	++um->priv->running_not_undoable_actions;
-}
-
-static void
-gtk_source_undo_manager_end_not_undoable_action_internal (GtkSourceUndoManager *um)
-{
-	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
-	g_return_if_fail (um->priv != NULL);
-
-	g_return_if_fail (um->priv->running_not_undoable_actions > 0);
-
-	--um->priv->running_not_undoable_actions;
-}
-
-void
-gtk_source_undo_manager_end_not_undoable_action (GtkSourceUndoManager *um)
-{
-	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
-	g_return_if_fail (um->priv != NULL);
-
-	gtk_source_undo_manager_end_not_undoable_action_internal (um);
-
-	if (um->priv->running_not_undoable_actions == 0)
-	{
-		gtk_source_undo_manager_free_action_list (um);
-
-		um->priv->next_redo = -1;
-
-		if (um->priv->can_undo)
-		{
-			um->priv->can_undo = FALSE;
-			g_signal_emit (G_OBJECT (um),
-				       undo_manager_signals [CAN_UNDO],
-				       0,
-				       FALSE);
-		}
-
-		if (um->priv->can_redo)
-		{
-			um->priv->can_redo = FALSE;
-			g_signal_emit (G_OBJECT (um),
-				       undo_manager_signals [CAN_REDO],
-				       0,
-				       FALSE);
-		}
-	}
-}
-
-gboolean
-gtk_source_undo_manager_can_undo (const GtkSourceUndoManager *um)
+static gboolean
+gtk_source_undo_manager_can_undo_default (GtkSourceUndoManager *manager)
 {
-	g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE);
-	g_return_val_if_fail (um->priv != NULL, FALSE);
-
-	return um->priv->can_undo;
+	return FALSE;
 }
 
-gboolean
-gtk_source_undo_manager_can_redo (const GtkSourceUndoManager *um)
+static gboolean
+gtk_source_undo_manager_can_redo_default (GtkSourceUndoManager *manager)
 {
-	g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE);
-	g_return_val_if_fail (um->priv != NULL, FALSE);
-
-	return um->priv->can_redo;
+	return FALSE;
 }
 
 static void
-set_cursor (GtkTextBuffer *buffer, gint cursor)
+gtk_source_undo_manager_undo_default (GtkSourceUndoManager *manager)
 {
-	GtkTextIter iter;
-
-	/* Place the cursor at the requested position */
-	gtk_text_buffer_get_iter_at_offset (buffer, &iter, cursor);
-	gtk_text_buffer_place_cursor (buffer, &iter);
 }
 
 static void
-insert_text (GtkTextBuffer *buffer, gint pos, const gchar *text, gint len)
+gtk_source_undo_manager_redo_default (GtkSourceUndoManager *manager)
 {
-	GtkTextIter iter;
-
-	gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos);
-	gtk_text_buffer_insert (buffer, &iter, text, len);
 }
 
 static void
-delete_text (GtkTextBuffer *buffer, gint start, gint end)
-{
-	GtkTextIter start_iter;
-	GtkTextIter end_iter;
-
-	gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
-
-	if (end < 0)
-		gtk_text_buffer_get_end_iter (buffer, &end_iter);
-	else
-		gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
-
-	gtk_text_buffer_delete (buffer, &start_iter, &end_iter);
-}
-
-static gchar*
-get_chars (GtkTextBuffer *buffer, gint start, gint end)
-{
-	GtkTextIter start_iter;
-	GtkTextIter end_iter;
-
-	gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
-
-	if (end < 0)
-		gtk_text_buffer_get_end_iter (buffer, &end_iter);
-	else
-		gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
-
-	return gtk_text_buffer_get_slice (buffer, &start_iter, &end_iter, TRUE);
-}
-
-static GtkSourceUndoAction *
-action_list_nth_data (GPtrArray *array,
-		      gint       n)
+gtk_source_undo_manager_begin_not_undoable_action_default (GtkSourceUndoManager *manager)
 {
-	if (n < 0 || n >= (gint)array->len)
-		return NULL;
-	else
-		return array->pdata[array->len - 1 - n];
 }
 
 static void
-action_list_prepend (GPtrArray           *array,
-		     GtkSourceUndoAction *action)
-{
-	g_ptr_array_add (array, action);
-}
-
-static GtkSourceUndoAction *
-action_list_last_data (GPtrArray *array)
+gtk_source_undo_manager_end_not_undoable_action_default (GtkSourceUndoManager *manager)
 {
-	if (array->len != 0)
-		return array->pdata[0];
-	else
-		return NULL;
 }
 
 static void
-action_list_delete_last (GPtrArray *array)
-{
-	if (array->len != 0)
-	{
-		memmove (&array->pdata[0], &array->pdata[1], (array->len - 1)*sizeof (gpointer));
-		g_ptr_array_set_size (array, array->len - 1);
-	}
-}
-
-void
-gtk_source_undo_manager_undo (GtkSourceUndoManager *um)
+gtk_source_undo_manager_init (GtkSourceUndoManagerIface *iface)
 {
-	GtkSourceUndoAction *undo_action;
-	gboolean modified = FALSE;
-	gint cursor_pos = -1;
+	static gboolean initialized = FALSE;
 
-	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
-	g_return_if_fail (um->priv != NULL);
-	g_return_if_fail (um->priv->can_undo);
+	iface->can_undo = gtk_source_undo_manager_can_undo_default;
+	iface->can_redo = gtk_source_undo_manager_can_redo_default;
 
-	um->priv->modified_undoing_group = FALSE;
+	iface->undo = gtk_source_undo_manager_undo_default;
+	iface->redo = gtk_source_undo_manager_redo_default;
 
-	gtk_source_undo_manager_begin_not_undoable_action (um);
+	iface->begin_not_undoable_action = gtk_source_undo_manager_begin_not_undoable_action_default;
+	iface->end_not_undoable_action = gtk_source_undo_manager_end_not_undoable_action_default;
 
-	do
+	if (!initialized)
 	{
-		undo_action = action_list_nth_data (um->priv->actions, um->priv->next_redo + 1);
-		g_return_if_fail (undo_action != NULL);
-
-		/* undo_action->modified can be TRUE only if undo_action->order_in_group <= 1 */
-		g_return_if_fail ((undo_action->order_in_group <= 1) ||
-				  ((undo_action->order_in_group > 1) && !undo_action->modified));
-
-		if (undo_action->order_in_group <= 1)
-		{
-			/* Set modified to TRUE only if the buffer did not change its state from
-			 * "not modified" to "modified" undoing an action (with order_in_group > 1)
-			 * in current group. */
-			modified = (undo_action->modified && !um->priv->modified_undoing_group);
-		}
-
-		switch (undo_action->action_type)
-		{
-			case GTK_SOURCE_UNDO_ACTION_DELETE:
-				insert_text (
-					um->priv->document,
-					undo_action->action.delete.start,
-					undo_action->action.delete.text,
-					strlen (undo_action->action.delete.text));
-
-				if (undo_action->action.delete.forward)
-					cursor_pos = undo_action->action.delete.start;
-				else
-					cursor_pos = undo_action->action.delete.end;
-
-				break;
-
-			case GTK_SOURCE_UNDO_ACTION_INSERT:
-				delete_text (
-					um->priv->document,
-					undo_action->action.insert.pos,
-					undo_action->action.insert.pos +
-						undo_action->action.insert.chars);
-
-				cursor_pos = undo_action->action.insert.pos;
-				break;
-
-			default:
-				/* Unknown action type. */
-				g_return_if_reached ();
-		}
-
-		++um->priv->next_redo;
-
-	} while (undo_action->order_in_group > 1);
-
-	if (cursor_pos >= 0)
-		set_cursor (um->priv->document, cursor_pos);
-
-	if (modified)
-	{
-		--um->priv->next_redo;
-		gtk_text_buffer_set_modified (um->priv->document, FALSE);
-		++um->priv->next_redo;
-	}
-
-	gtk_source_undo_manager_end_not_undoable_action_internal (um);
-
-	um->priv->modified_undoing_group = FALSE;
-
-	if (!um->priv->can_redo)
-	{
-		um->priv->can_redo = TRUE;
-		g_signal_emit (G_OBJECT (um),
-			       undo_manager_signals [CAN_REDO],
-			       0,
-			       TRUE);
-	}
+		/**
+		 * GtkSourceUndoManager::can-undo-changed:
+		 * @manager: The #GtkSourceUndoManager
+		 *
+		 * Emitted when the ability to undo has changed.
+		 *
+		 */
+		signals[CAN_UNDO_CHANGED] =
+			g_signal_new ("can-undo-changed",
+			      G_TYPE_FROM_INTERFACE (iface),
+			      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+			      G_STRUCT_OFFSET (GtkSourceUndoManagerIface, can_undo_changed),
+			      NULL,
+			      NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
+
+		/**
+		 * GtkSourceUndoManager::can-redo-changed:
+		 * @manager: The #GtkSourceUndoManager
+		 *
+		 * Emitted when the ability to redo has changed.
+		 *
+		 */
+		signals[CAN_REDO_CHANGED] =
+			g_signal_new ("can-redo-changed",
+			      G_TYPE_FROM_INTERFACE (iface),
+			      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+			      G_STRUCT_OFFSET (GtkSourceUndoManagerIface, can_redo_changed),
+			      NULL,
+			      NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
 
-	if (um->priv->next_redo >= (gint)um->priv->actions->len - 1)
-	{
-		um->priv->can_undo = FALSE;
-		g_signal_emit (G_OBJECT (um),
-			       undo_manager_signals [CAN_UNDO],
-			       0,
-			       FALSE);
+		initialized = TRUE;
 	}
 }
 
-void
-gtk_source_undo_manager_redo (GtkSourceUndoManager *um)
+GType
+gtk_source_undo_manager_get_type ()
 {
-	GtkSourceUndoAction *undo_action;
-	gboolean modified = FALSE;
-	gint cursor_pos = -1;
-
-	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
-	g_return_if_fail (um->priv != NULL);
-	g_return_if_fail (um->priv->can_redo);
-
-	undo_action = action_list_nth_data (um->priv->actions, um->priv->next_redo);
-	g_return_if_fail (undo_action != NULL);
-
-	gtk_source_undo_manager_begin_not_undoable_action (um);
-
-	do
+	static GType gtk_source_undo_manager_type_id = 0;
+	
+	if (!gtk_source_undo_manager_type_id)
 	{
-		if (undo_action->modified)
-		{
-			g_return_if_fail (undo_action->order_in_group <= 1);
-			modified = TRUE;
-		}
-
-		--um->priv->next_redo;
-
-		switch (undo_action->action_type)
+		static const GTypeInfo g_define_type_info =
 		{
-			case GTK_SOURCE_UNDO_ACTION_DELETE:
-				delete_text (
-					um->priv->document,
-					undo_action->action.delete.start,
-					undo_action->action.delete.end);
-
-				cursor_pos = undo_action->action.delete.start;
-
-				break;
-
-			case GTK_SOURCE_UNDO_ACTION_INSERT:
-				cursor_pos = undo_action->action.insert.pos +
-						undo_action->action.insert.length;
-
-				insert_text (
-					um->priv->document,
-					undo_action->action.insert.pos,
-					undo_action->action.insert.text,
-					undo_action->action.insert.length);
-
-				break;
-
-			default:
-				/* Unknown action type */
-				++um->priv->next_redo;
-				g_return_if_reached ();
-		}
-
-		if (um->priv->next_redo < 0)
-			undo_action = NULL;
-		else
-			undo_action = action_list_nth_data (um->priv->actions, um->priv->next_redo);
-
-	} while ((undo_action != NULL) && (undo_action->order_in_group > 1));
-
-	if (cursor_pos >= 0)
-		set_cursor (um->priv->document, cursor_pos);
-
-	if (modified)
-	{
-		++um->priv->next_redo;
-		gtk_text_buffer_set_modified (um->priv->document, FALSE);
-		--um->priv->next_redo;
-	}
-
-	gtk_source_undo_manager_end_not_undoable_action_internal (um);
-
-	if (um->priv->next_redo < 0)
-	{
-		um->priv->can_redo = FALSE;
-		g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE);
-	}
-
-	if (!um->priv->can_undo)
-	{
-		um->priv->can_undo = TRUE;
-		g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, TRUE);
-	}
-}
-
-static void
-gtk_source_undo_action_free (GtkSourceUndoAction *action)
-{
-	if (action == NULL)
-		return;
-
-	if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
-		g_free (action->action.insert.text);
-	else if (action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
-		g_free (action->action.delete.text);
-	else
-		g_return_if_reached ();
-
-	g_free (action);
-}
-
-static void
-gtk_source_undo_manager_free_action_list (GtkSourceUndoManager *um)
-{
-	gint i;
-
-	for (i = (gint)um->priv->actions->len - 1; i >= 0; i--)
-	{
-		GtkSourceUndoAction *action = um->priv->actions->pdata[i];
-
-		if (action->order_in_group == 1)
-			--um->priv->num_of_groups;
-
-		if (action->modified)
-			um->priv->modified_action = INVALID;
-
-		gtk_source_undo_action_free (action);
-	}
-
-	/* Some arbitrary limit, to avoid wasting space */
-	if (um->priv->actions->len > 2048)
-	{
-		g_ptr_array_free (um->priv->actions, TRUE);
-		um->priv->actions = g_ptr_array_new ();
-	}
-	else
-	{
-		g_ptr_array_set_size (um->priv->actions, 0);
+			sizeof (GtkSourceUndoManagerIface),
+			(GBaseInitFunc) gtk_source_undo_manager_init,
+			NULL,
+			NULL,
+			NULL,
+			NULL,
+			0,
+			0,
+			NULL
+		};
+		
+		gtk_source_undo_manager_type_id =
+			g_type_register_static (G_TYPE_INTERFACE,
+						"GtkSourceUndoManager",
+						&g_define_type_info,
+						0);
+
+		g_type_interface_add_prerequisite (gtk_source_undo_manager_type_id,
+		                                   G_TYPE_OBJECT);
 	}
+	
+	return gtk_source_undo_manager_type_id;
 }
 
-static void
-gtk_source_undo_manager_insert_text_handler (GtkTextBuffer 		*buffer,
-					     GtkTextIter 		*pos,
-		                             const gchar 		*text,
-					     gint 			 length,
-					     GtkSourceUndoManager 	*um)
-{
-	GtkSourceUndoAction undo_action;
-
-	if (um->priv->running_not_undoable_actions > 0)
-		return;
-
-	undo_action.action_type = GTK_SOURCE_UNDO_ACTION_INSERT;
-
-	undo_action.action.insert.pos    = gtk_text_iter_get_offset (pos);
-	undo_action.action.insert.text   = (gchar*) text;
-	undo_action.action.insert.length = length;
-	undo_action.action.insert.chars  = g_utf8_strlen (text, length);
-
-	if ((undo_action.action.insert.chars > 1) || (g_utf8_get_char (text) == '\n'))
-
-	       	undo_action.mergeable = FALSE;
-	else
-		undo_action.mergeable = TRUE;
-
-	undo_action.modified = FALSE;
-
-	gtk_source_undo_manager_add_action (um, &undo_action);
-}
-
-static void
-gtk_source_undo_manager_delete_range_handler (GtkTextBuffer 		*buffer,
-					      GtkTextIter 		*start,
-                        		      GtkTextIter 		*end,
-					      GtkSourceUndoManager 	*um)
+/**
+ * gtk_source_undo_manager_can_undo:
+ * @manager: A #GtkSourceUndoManager
+ *
+ * Get whether there are undo operations available.
+ *
+ * Returns: %TRUE if there are undo operations available, %FALSE otherwise
+ */
+gboolean
+gtk_source_undo_manager_can_undo (GtkSourceUndoManager *manager)
 {
-	GtkSourceUndoAction undo_action;
-	GtkTextIter insert_iter;
-
-	if (um->priv->running_not_undoable_actions > 0)
-		return;
-
-	undo_action.action_type = GTK_SOURCE_UNDO_ACTION_DELETE;
-
-	gtk_text_iter_order (start, end);
-
-	undo_action.action.delete.start  = gtk_text_iter_get_offset (start);
-	undo_action.action.delete.end    = gtk_text_iter_get_offset (end);
-
-	undo_action.action.delete.text   = get_chars (
-						buffer,
-						undo_action.action.delete.start,
-						undo_action.action.delete.end);
-
-	/* figure out if the user used the Delete or the Backspace key */
-	gtk_text_buffer_get_iter_at_mark (buffer, &insert_iter,
-					  gtk_text_buffer_get_insert (buffer));
-	if (gtk_text_iter_get_offset (&insert_iter) <= undo_action.action.delete.start)
-		undo_action.action.delete.forward = TRUE;
-	else
-		undo_action.action.delete.forward = FALSE;
-
-	if (((undo_action.action.delete.end - undo_action.action.delete.start) > 1) ||
-	     (g_utf8_get_char (undo_action.action.delete.text  ) == '\n'))
-	       	undo_action.mergeable = FALSE;
-	else
-		undo_action.mergeable = TRUE;
-
-	undo_action.modified = FALSE;
-
-	gtk_source_undo_manager_add_action (um, &undo_action);
-
-	g_free (undo_action.action.delete.text);
-
+	g_return_val_if_fail (GTK_IS_SOURCE_UNDO_MANAGER (manager), FALSE);
+	return GTK_SOURCE_UNDO_MANAGER_GET_INTERFACE (manager)->can_undo (manager);
 }
 
-static void
-gtk_source_undo_manager_begin_user_action_handler (GtkTextBuffer *buffer, GtkSourceUndoManager *um)
+/**
+ * gtk_source_undo_manager_can_redo:
+ * @manager: A #GtkSourceUndoManager
+ *
+ * Get whether there are redo operations available.
+ *
+ * Returns: %TRUE if there are redo operations available, %FALSE otherwise
+ */
+gboolean
+gtk_source_undo_manager_can_redo (GtkSourceUndoManager *manager)
 {
-	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
-	g_return_if_fail (um->priv != NULL);
-
-	if (um->priv->running_not_undoable_actions > 0)
-		return;
-
-	um->priv->actions_in_current_group = 0;
+	g_return_val_if_fail (GTK_IS_SOURCE_UNDO_MANAGER (manager), FALSE);
+	return GTK_SOURCE_UNDO_MANAGER_GET_INTERFACE (manager)->can_redo (manager);
 }
 
-static void
-gtk_source_undo_manager_add_action (GtkSourceUndoManager 	*um,
-				    const GtkSourceUndoAction 	*undo_action)
+/**
+ * gtk_source_undo_manager_undo:
+ * @manager: A #GtkSourceUndoManager
+ *
+ * Perform a single undo. Calling this function when there are no undo operations
+ * available is an error. Use #gtk_source_undo_manager_can_undo to find out
+ * if there are undo operations available.
+ *
+ */
+void
+gtk_source_undo_manager_undo (GtkSourceUndoManager *manager)
 {
-	GtkSourceUndoAction* action;
-
-	if (um->priv->next_redo >= 0)
-	{
-		gtk_source_undo_manager_free_first_n_actions (um, um->priv->next_redo + 1);
-	}
-
-	um->priv->next_redo = -1;
-
-	if (!gtk_source_undo_manager_merge_action (um, undo_action))
-	{
-		action = g_new (GtkSourceUndoAction, 1);
-		*action = *undo_action;
-
-		if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
-			action->action.insert.text = g_strndup (undo_action->action.insert.text, undo_action->action.insert.length);
-		else if (action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
-			action->action.delete.text = g_strdup (undo_action->action.delete.text);
-		else
-		{
-			g_free (action);
-			g_return_if_reached ();
-		}
-
-		++um->priv->actions_in_current_group;
-		action->order_in_group = um->priv->actions_in_current_group;
-
-		if (action->order_in_group == 1)
-			++um->priv->num_of_groups;
-
-		action_list_prepend (um->priv->actions, action);
-	}
-
-	gtk_source_undo_manager_check_list_size (um);
-
-	if (!um->priv->can_undo)
-	{
-		um->priv->can_undo = TRUE;
-		g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, TRUE);
-	}
-
-	if (um->priv->can_redo)
-	{
-		um->priv->can_redo = FALSE;
-		g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE);
-	}
+	g_return_if_fail (GTK_IS_SOURCE_UNDO_MANAGER (manager));
+	GTK_SOURCE_UNDO_MANAGER_GET_INTERFACE (manager)->undo (manager);
 }
 
-static void
-gtk_source_undo_manager_free_first_n_actions (GtkSourceUndoManager	*um,
-					      gint 			 n)
+/**
+ * gtk_source_undo_manager_redo:
+ * @manager: A #GtkSourceUndoManager
+ *
+ * Perform a single redo. Calling this function when there are no redo operations
+ * available is an error. Use #gtk_source_undo_manager_can_redo to find out
+ * if there are redo operations available.
+ *
+ */
+void
+gtk_source_undo_manager_redo (GtkSourceUndoManager *manager)
 {
-	gint i;
-
-	if (um->priv->actions->len == 0)
-		return;
-
-	for (i = 0; i < n; i++)
-	{
-		GtkSourceUndoAction *action = um->priv->actions->pdata[um->priv->actions->len - 1];
-
-		if (action->order_in_group == 1)
-			--um->priv->num_of_groups;
-
-		if (action->modified)
-			um->priv->modified_action = INVALID;
-
-		gtk_source_undo_action_free (action);
-
-		g_ptr_array_set_size (um->priv->actions, um->priv->actions->len - 1);
-
-		if (um->priv->actions->len == 0)
-			return;
-	}
-}
-
-static void
-gtk_source_undo_manager_check_list_size (GtkSourceUndoManager *um)
-{
-	gint undo_levels;
-
-	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
-	g_return_if_fail (um->priv != NULL);
-
-	undo_levels = gtk_source_undo_manager_get_max_undo_levels (um);
-
-	if (undo_levels < 1)
-		return;
-
-	if (um->priv->num_of_groups > undo_levels)
-	{
-		GtkSourceUndoAction *undo_action;
-
-		undo_action = action_list_last_data (um->priv->actions);
-
-		do
-		{
-			if (undo_action->order_in_group == 1)
-				--um->priv->num_of_groups;
-
-			if (undo_action->modified)
-				um->priv->modified_action = INVALID;
-
-			gtk_source_undo_action_free (undo_action);
-
-			action_list_delete_last (um->priv->actions);
-
-			undo_action = action_list_last_data (um->priv->actions);
-			g_return_if_fail (undo_action != NULL);
-
-		} while ((undo_action->order_in_group > 1) ||
-			 (um->priv->num_of_groups > undo_levels));
-	}
+	g_return_if_fail (GTK_IS_SOURCE_UNDO_MANAGER (manager));
+	GTK_SOURCE_UNDO_MANAGER_GET_INTERFACE (manager)->redo (manager);
 }
 
 /**
- * gtk_source_undo_manager_merge_action:
- * @um: a #GtkSourceUndoManager.
- * @undo_action: a #GtkSourceUndoAction.
+ * gtk_source_undo_manager_begin_not_undoable_action:
+ * @manager: A #GtkSourceUndoManager
  *
- * This function tries to merge the undo action at the top of
- * the stack with a new undo action. So when we undo for example
- * typing, we can undo the whole word and not each letter by itself.
+ * Begin a not undoable action on the buffer. All changes between this call
+ * and the call to #gtk_source_undo_manager_end_not_undoable_action cannot
+ * be undone. This function should be re-entrant.
  *
- * Return Value: %TRUE is merge was sucessful, %FALSE otherwise.
- **/
-static gboolean
-gtk_source_undo_manager_merge_action (GtkSourceUndoManager 	*um,
-				      const GtkSourceUndoAction *undo_action)
+ */
+void
+gtk_source_undo_manager_begin_not_undoable_action (GtkSourceUndoManager *manager)
 {
-	GtkSourceUndoAction *last_action;
-
-	g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE);
-	g_return_val_if_fail (um->priv != NULL, FALSE);
-
-	if (um->priv->actions->len == 0)
-		return FALSE;
-
-	last_action = action_list_nth_data (um->priv->actions, 0);
-
-	if (!last_action->mergeable)
-		return FALSE;
-
-	if ((!undo_action->mergeable) ||
-	    (undo_action->action_type != last_action->action_type))
-	{
-		last_action->mergeable = FALSE;
-		return FALSE;
-	}
-
-	if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
-	{
-		if ((last_action->action.delete.forward != undo_action->action.delete.forward) ||
-		    ((last_action->action.delete.start != undo_action->action.delete.start) &&
-		     (last_action->action.delete.start != undo_action->action.delete.end)))
-		{
-			last_action->mergeable = FALSE;
-			return FALSE;
-		}
-
-		if (last_action->action.delete.start == undo_action->action.delete.start)
-		{
-			gchar *str;
-
-#define L  (last_action->action.delete.end - last_action->action.delete.start - 1)
-#define g_utf8_get_char_at(p,i) g_utf8_get_char(g_utf8_offset_to_pointer((p),(i)))
-
-			/* Deleted with the delete key */
-			if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') &&
-			    (g_utf8_get_char (undo_action->action.delete.text) != '\t') &&
-                            ((g_utf8_get_char_at (last_action->action.delete.text, L) == ' ') ||
-			     (g_utf8_get_char_at (last_action->action.delete.text, L)  == '\t')))
-			{
-				last_action->mergeable = FALSE;
-				return FALSE;
-			}
-
-			str = g_strdup_printf ("%s%s", last_action->action.delete.text,
-				undo_action->action.delete.text);
-
-			g_free (last_action->action.delete.text);
-			last_action->action.delete.end += (undo_action->action.delete.end -
-							   undo_action->action.delete.start);
-			last_action->action.delete.text = str;
-		}
-		else
-		{
-			gchar *str;
-
-			/* Deleted with the backspace key */
-			if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') &&
-			    (g_utf8_get_char (undo_action->action.delete.text) != '\t') &&
-                            ((g_utf8_get_char (last_action->action.delete.text) == ' ') ||
-			     (g_utf8_get_char (last_action->action.delete.text) == '\t')))
-			{
-				last_action->mergeable = FALSE;
-				return FALSE;
-			}
-
-			str = g_strdup_printf ("%s%s", undo_action->action.delete.text,
-				last_action->action.delete.text);
-
-			g_free (last_action->action.delete.text);
-			last_action->action.delete.start = undo_action->action.delete.start;
-			last_action->action.delete.text = str;
-		}
-	}
-	else if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
-	{
-		gchar* str;
-
-#define I (last_action->action.insert.chars - 1)
-
-		if ((undo_action->action.insert.pos !=
-		     	(last_action->action.insert.pos + last_action->action.insert.chars)) ||
-		    ((g_utf8_get_char (undo_action->action.insert.text) != ' ') &&
-		      (g_utf8_get_char (undo_action->action.insert.text) != '\t') &&
-		     ((g_utf8_get_char_at (last_action->action.insert.text, I) == ' ') ||
-		      (g_utf8_get_char_at (last_action->action.insert.text, I) == '\t')))
-		   )
-		{
-			last_action->mergeable = FALSE;
-			return FALSE;
-		}
-
-		str = g_strdup_printf ("%s%s", last_action->action.insert.text,
-				undo_action->action.insert.text);
-
-		g_free (last_action->action.insert.text);
-		last_action->action.insert.length += undo_action->action.insert.length;
-		last_action->action.insert.text = str;
-		last_action->action.insert.chars += undo_action->action.insert.chars;
-
-	}
-	else
-		/* Unknown action inside undo merge encountered */
-		g_return_val_if_reached (TRUE);
-
-	return TRUE;
+	g_return_if_fail (GTK_IS_SOURCE_UNDO_MANAGER (manager));
+	GTK_SOURCE_UNDO_MANAGER_GET_INTERFACE (manager)->begin_not_undoable_action (manager);
 }
 
-gint
-gtk_source_undo_manager_get_max_undo_levels (GtkSourceUndoManager *um)
+/**
+ * gtk_source_undo_manager_end_not_undoable_action:
+ * @manager: A #GtkSourceUndoManager
+ *
+ * Ends a not undoable action on the buffer.
+ *
+ */
+void
+gtk_source_undo_manager_end_not_undoable_action (GtkSourceUndoManager *manager)
 {
-	g_return_val_if_fail (um != NULL, 0);
-	g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), 0);
-
-	return um->priv->max_undo_levels;
+	g_return_if_fail (GTK_IS_SOURCE_UNDO_MANAGER (manager));
+	GTK_SOURCE_UNDO_MANAGER_GET_INTERFACE (manager)->end_not_undoable_action (manager);
 }
 
+/**
+ * gtk_source_undo_manager_can_undo_changed:
+ * @manager: A #GtkSourceUndoManager
+ * 
+ * Emits the ::can-undo-changed signal.
+ *
+ **/
 void
-gtk_source_undo_manager_set_max_undo_levels (GtkSourceUndoManager	*um,
-				  	     gint			 max_undo_levels)
+gtk_source_undo_manager_can_undo_changed (GtkSourceUndoManager *manager)
 {
-	gint old_levels;
-
-	g_return_if_fail (um != NULL);
-	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
-
-	old_levels = um->priv->max_undo_levels;
-	um->priv->max_undo_levels = max_undo_levels;
-
-	if (max_undo_levels < 1)
-		return;
+	g_return_if_fail (GTK_IS_SOURCE_UNDO_MANAGER (manager));
 
-	if (old_levels > max_undo_levels)
-	{
-		/* strip redo actions first */
-		while (um->priv->next_redo >= 0 && (um->priv->num_of_groups > max_undo_levels))
-		{
-			gtk_source_undo_manager_free_first_n_actions (um, 1);
-			um->priv->next_redo--;
-		}
-
-		/* now remove undo actions if necessary */
-		gtk_source_undo_manager_check_list_size (um);
-
-		/* emit "can_undo" and/or "can_redo" if appropiate */
-		if (um->priv->next_redo < 0 && um->priv->can_redo)
-		{
-			um->priv->can_redo = FALSE;
-			g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE);
-		}
-
-		if (um->priv->can_undo &&
-		    um->priv->next_redo >= (gint)um->priv->actions->len - 1)
-		{
-			um->priv->can_undo = FALSE;
-			g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, FALSE);
-		}
-	}
+	g_signal_emit (manager, signals[CAN_UNDO_CHANGED], 0);
 }
 
-static void
-gtk_source_undo_manager_modified_changed_handler (GtkTextBuffer        *buffer,
-                                            	  GtkSourceUndoManager *um)
+/**
+ * gtk_source_undo_manager_can_redo_changed:
+ * @manager: A #GtkSourceUndoManager
+ * 
+ * Emits the ::can-redo-changed signal.
+ *
+ **/
+void
+gtk_source_undo_manager_can_redo_changed (GtkSourceUndoManager *manager)
 {
-	GtkSourceUndoAction *action;
-	gint idx;
-
-	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
-	g_return_if_fail (um->priv != NULL);
-
-	if (um->priv->actions->len == 0)
-		return;
-
-	idx = um->priv->next_redo + 1;
-	action = action_list_nth_data (um->priv->actions, idx);
-
-	if (gtk_text_buffer_get_modified (buffer) == FALSE)
-	{
-		if (action != NULL)
-			action->mergeable = FALSE;
-
-		if (um->priv->modified_action != NULL)
-		{
-			if (um->priv->modified_action != INVALID)
-				um->priv->modified_action->modified = FALSE;
-
-			um->priv->modified_action = NULL;
-		}
-
-		return;
-	}
-
-	if (action == NULL)
-	{
-		g_return_if_fail (um->priv->running_not_undoable_actions > 0);
-
-		return;
-	}
-
-	/* gtk_text_buffer_get_modified (buffer) == TRUE */
-
-// 	g_return_if_fail (um->priv->modified_action == NULL);
-	if (um->priv->modified_action != NULL)
-	{
-		g_message ("%s: oops", G_STRLOC);
-		return;
-	}
-
-	if (action->order_in_group > 1)
-		um->priv->modified_undoing_group  = TRUE;
-
-	while (action->order_in_group > 1)
-	{
-		action = action_list_nth_data (um->priv->actions, ++idx);
-		g_return_if_fail (action != NULL);
-	}
+	g_return_if_fail (GTK_IS_SOURCE_UNDO_MANAGER (manager));
 
-	action->modified = TRUE;
-	um->priv->modified_action = action;
+	g_signal_emit (manager, signals[CAN_REDO_CHANGED], 0);
 }
diff --git a/gtksourceview/gtksourceundomanager.h b/gtksourceview/gtksourceundomanager.h
index 0d3e03c..175a468 100644
--- a/gtksourceview/gtksourceundomanager.h
+++ b/gtksourceview/gtksourceundomanager.h
@@ -25,56 +25,55 @@
 #ifndef __GTK_SOURCE_UNDO_MANAGER_H__
 #define __GTK_SOURCE_UNDO_MANAGER_H__
 
-#include <gtk/gtktextbuffer.h>
+#include <gtk/gtk.h>
 
-#define GTK_TYPE_SOURCE_UNDO_MANAGER		(gtk_source_undo_manager_get_type ())
-#define GTK_SOURCE_UNDO_MANAGER(obj)		(G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_SOURCE_UNDO_MANAGER, GtkSourceUndoManager))
-#define GTK_SOURCE_UNDO_MANAGER_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_SOURCE_UNDO_MANAGER, GtkSourceUndoManagerClass))
-#define GTK_SOURCE_IS_UNDO_MANAGER(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SOURCE_UNDO_MANAGER))
-#define GTK_SOURCE_IS_UNDO_MANAGER_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SOURCE_UNDO_MANAGER))
-#define GTK_SOURCE_UNDO_MANAGER_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SOURCE_UNDO_MANAGER, GtkSourceUndoManagerClass))
+G_BEGIN_DECLS
 
+#define GTK_TYPE_SOURCE_UNDO_MANAGER                (gtk_source_undo_manager_get_type ())
+#define GTK_SOURCE_UNDO_MANAGER(obj)                (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SOURCE_UNDO_MANAGER, GtkSourceUndoManager))
+#define GTK_IS_SOURCE_UNDO_MANAGER(obj)             (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SOURCE_UNDO_MANAGER))
+#define GTK_SOURCE_UNDO_MANAGER_GET_INTERFACE(obj)  (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GTK_TYPE_SOURCE_UNDO_MANAGER, GtkSourceUndoManagerIface))
 
 typedef struct _GtkSourceUndoManager        	GtkSourceUndoManager;
-typedef struct _GtkSourceUndoManagerClass 	GtkSourceUndoManagerClass;
-typedef struct _GtkSourceUndoManagerPrivate 	GtkSourceUndoManagerPrivate;
+typedef struct _GtkSourceUndoManagerIface      	GtkSourceUndoManagerIface;
 
-struct _GtkSourceUndoManager
+struct _GtkSourceUndoManagerIface
 {
-	GObject base;
+	GTypeInterface parent;
 
-	GtkSourceUndoManagerPrivate *priv;
-};
+	/* Interface functions */
+	void     (*set_buffer)                (GtkSourceUndoManager *manager,
+	                                       GtkTextBuffer        *buffer);
 
-struct _GtkSourceUndoManagerClass
-{
-	GObjectClass parent_class;
+	gboolean (*can_undo)                  (GtkSourceUndoManager *manager);
+	gboolean (*can_redo)                  (GtkSourceUndoManager *manager);
+
+	void     (*undo)                      (GtkSourceUndoManager *manager);
+	void     (*redo)                      (GtkSourceUndoManager *manager);
+
+	void     (*begin_not_undoable_action) (GtkSourceUndoManager *manager);
+	void     (*end_not_undoable_action)   (GtkSourceUndoManager *manager);
 
 	/* Signals */
-	void (*can_undo) (GtkSourceUndoManager *um, gboolean can_undo);
-    	void (*can_redo) (GtkSourceUndoManager *um, gboolean can_redo);
+	void     (*can_undo_changed)          (GtkSourceUndoManager *manager);
+	void     (*can_redo_changed)          (GtkSourceUndoManager *manager);
 };
 
-GType        		gtk_source_undo_manager_get_type	(void) G_GNUC_CONST;
+GType     gtk_source_undo_manager_get_type                  (void) G_GNUC_CONST;
 
-GtkSourceUndoManager* 	gtk_source_undo_manager_new 		(GtkTextBuffer 		*buffer);
+gboolean  gtk_source_undo_manager_can_undo                  (GtkSourceUndoManager *manager);
+gboolean  gtk_source_undo_manager_can_redo                  (GtkSourceUndoManager *manager);
 
-gboolean		gtk_source_undo_manager_can_undo	(const GtkSourceUndoManager *um);
-gboolean		gtk_source_undo_manager_can_redo 	(const GtkSourceUndoManager *um);
+void      gtk_source_undo_manager_undo                      (GtkSourceUndoManager *manager);
+void      gtk_source_undo_manager_redo                      (GtkSourceUndoManager *manager);
 
-void			gtk_source_undo_manager_undo 		(GtkSourceUndoManager 	*um);
-void			gtk_source_undo_manager_redo 		(GtkSourceUndoManager 	*um);
+void      gtk_source_undo_manager_begin_not_undoable_action (GtkSourceUndoManager *manager);
+void      gtk_source_undo_manager_end_not_undoable_action   (GtkSourceUndoManager *manager);
 
-void			gtk_source_undo_manager_begin_not_undoable_action
-								(GtkSourceUndoManager	*um);
-void			gtk_source_undo_manager_end_not_undoable_action
-								(GtkSourceUndoManager	*um);
+void      gtk_source_undo_manager_can_undo_changed          (GtkSourceUndoManager *manager);
+void      gtk_source_undo_manager_can_redo_changed          (GtkSourceUndoManager *manager);
 
-gint			gtk_source_undo_manager_get_max_undo_levels
-								(GtkSourceUndoManager 	*um);
-void			gtk_source_undo_manager_set_max_undo_levels
-								(GtkSourceUndoManager 	*um,
-				  	     			 gint		 	 undo_levels);
+G_END_DECLS
 
 #endif /* __GTK_SOURCE_UNDO_MANAGER_H__ */
 
diff --git a/gtksourceview/gtksourceundomanagerdefault.c b/gtksourceview/gtksourceundomanagerdefault.c
new file mode 100644
index 0000000..db7d03e
--- /dev/null
+++ b/gtksourceview/gtksourceundomanagerdefault.c
@@ -0,0 +1,1194 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * gtksourceundomanager.c
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002-2005  Paolo Maggi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "gtksourceundomanagerdefault.h"
+#include "gtksourceundomanager.h"
+#include "gtksourceview-i18n.h"
+
+#define DEFAULT_MAX_UNDO_LEVELS		-1
+
+/*
+ * The old code which used a GSList and g_slist_nth_element
+ * was way too slow for many operations (search/replace, hit Ctrl-Z,
+ * see gedit freezes). GSList was replaced with a GPtrArray with
+ * minimal changes to the code: to avoid insertions at the beginning
+ * of the array it uses array beginning as the list end and vice versa;
+ * hence bunch of ugly action_list_* functions.
+ * FIXME: so, somebody please rewrite this stuff using some nice data
+ * structures or something.
+ */
+
+typedef struct _GtkSourceUndoAction  			GtkSourceUndoAction;
+typedef struct _GtkSourceUndoInsertAction		GtkSourceUndoInsertAction;
+typedef struct _GtkSourceUndoDeleteAction		GtkSourceUndoDeleteAction;
+
+typedef enum
+{
+	GTK_SOURCE_UNDO_ACTION_INSERT,
+	GTK_SOURCE_UNDO_ACTION_DELETE
+} GtkSourceUndoActionType;
+
+/*
+ * We use offsets instead of GtkTextIters because the last ones
+ * require to much memory in this context without giving us any advantage.
+ */
+
+struct _GtkSourceUndoInsertAction
+{
+	gint   pos;
+	gchar *text;
+	gint   length;
+	gint   chars;
+};
+
+struct _GtkSourceUndoDeleteAction
+{
+	gint   start;
+	gint   end;
+	gchar *text;
+	gboolean forward;
+};
+
+struct _GtkSourceUndoAction
+{
+	GtkSourceUndoActionType action_type;
+
+	union
+	{
+		GtkSourceUndoInsertAction  insert;
+		GtkSourceUndoDeleteAction  delete;
+	} action;
+
+	gint order_in_group;
+
+	/* It is TRUE whether the action can be merged with the following action. */
+	guint mergeable : 1;
+
+	/* It is TRUE whether the action is marked as "modified".
+	 * An action is marked as "modified" if it changed the
+	 * state of the buffer from "not modified" to "modified". Only the first
+	 * action of a group can be marked as modified.
+	 * There can be a single action marked as "modified" in the actions list.
+	 */
+	guint modified  : 1;
+};
+
+/* INVALID is a pointer to an invalid action */
+#define INVALID ((void *) "IA")
+
+enum
+{
+	INSERT_TEXT,
+	DELETE_RANGE,
+	BEGIN_USER_ACTION,
+	MODIFIED_CHANGED,
+	NUM_SIGNALS
+};
+
+struct _GtkSourceUndoManagerDefaultPrivate
+{
+	GtkTextBuffer *buffer;
+
+	GPtrArray *actions;
+	gint next_redo;
+
+	gint actions_in_current_group;
+	gint running_not_undoable_actions;
+	gint num_of_groups;
+	gint max_undo_levels;
+
+	guint can_undo : 1;
+	guint can_redo : 1;
+
+	/* It is TRUE whether, while undoing an action of the current group (with order_in_group > 1),
+	 * the state of the buffer changed from "not modified" to "modified".
+	 */
+	guint modified_undoing_group : 1;
+
+	/* Pointer to the action (in the action list) marked as "modified".
+	 * It is NULL when no action is marked as "modified".
+	 * It is INVALID when the action marked as "modified" has been removed
+	 * from the action list (freeing the list or resizing it) */
+	GtkSourceUndoAction *modified_action;
+
+	guint buffer_signals[NUM_SIGNALS];
+};
+
+/* Properties */
+enum
+{
+	PROP_0,
+	PROP_BUFFER,
+	PROP_MAX_UNDO_LEVELS
+};
+
+static void insert_text_handler       (GtkTextBuffer             *buffer,
+                                       GtkTextIter               *pos,
+                                       const gchar               *text,
+                                       gint                       length,
+                                       GtkSourceUndoManagerDefault      *um);
+
+static void delete_range_handler      (GtkTextBuffer             *buffer,
+                                       GtkTextIter               *start,
+                                       GtkTextIter               *end,
+                                       GtkSourceUndoManagerDefault      *um);
+
+static void begin_user_action_handler (GtkTextBuffer             *buffer,
+                                       GtkSourceUndoManagerDefault      *um);
+
+static void modified_changed_handler  (GtkTextBuffer             *buffer,
+                                       GtkSourceUndoManagerDefault      *um);
+
+static void free_action_list          (GtkSourceUndoManagerDefault      *um);
+
+static void add_action                (GtkSourceUndoManagerDefault      *um,
+                                       const GtkSourceUndoAction *undo_action);
+static void free_first_n_actions      (GtkSourceUndoManagerDefault      *um,
+                                       gint                       n);
+static void check_list_size           (GtkSourceUndoManagerDefault      *um);
+
+static gboolean merge_action          (GtkSourceUndoManagerDefault      *um,
+                                       const GtkSourceUndoAction *undo_action);
+
+static void gtk_source_undo_manager_iface_init (GtkSourceUndoManagerIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GtkSourceUndoManagerDefault, gtk_source_undo_manager_default, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_SOURCE_UNDO_MANAGER,
+                                                gtk_source_undo_manager_iface_init))
+
+static void
+gtk_source_undo_manager_default_finalize (GObject *object)
+{
+	GtkSourceUndoManagerDefault *manager;
+
+	manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (object);
+
+	free_action_list (manager);
+	g_ptr_array_free (manager->priv->actions, TRUE);
+
+	G_OBJECT_CLASS (gtk_source_undo_manager_default_parent_class)->finalize (object);
+}
+
+static void
+clear_undo (GtkSourceUndoManagerDefault *manager)
+{
+	free_action_list (manager);
+
+	manager->priv->next_redo = -1;
+
+	if (manager->priv->can_undo)
+	{
+		manager->priv->can_undo = FALSE;
+		gtk_source_undo_manager_can_undo_changed (GTK_SOURCE_UNDO_MANAGER (manager));
+	}
+
+	if (manager->priv->can_redo)
+	{
+		manager->priv->can_redo = FALSE;
+		gtk_source_undo_manager_can_redo_changed (GTK_SOURCE_UNDO_MANAGER (manager));
+	}
+}
+
+static void
+set_buffer (GtkSourceUndoManagerDefault *manager,
+            GtkTextBuffer               *buffer)
+{
+	if (buffer == manager->priv->buffer)
+	{
+		return;
+	}
+
+	clear_undo (manager);
+
+	if (manager->priv->buffer != NULL)
+	{
+		gint i;
+
+		for (i = 0; i < NUM_SIGNALS; ++i)
+		{
+			g_signal_handler_disconnect (manager->priv->buffer,
+			                             manager->priv->buffer_signals[i]);
+		}
+
+		g_object_unref (manager->priv->buffer);
+		manager->priv->buffer = NULL;
+	}
+
+	if (buffer != NULL)
+	{
+		manager->priv->buffer = g_object_ref (buffer);
+
+		manager->priv->buffer_signals[INSERT_TEXT] =
+			g_signal_connect (buffer,
+			                  "insert-text",
+			                  G_CALLBACK (insert_text_handler),
+			                  manager);
+
+		manager->priv->buffer_signals[DELETE_RANGE] =
+			g_signal_connect (buffer,
+			                  "delete-range",
+			                  G_CALLBACK (delete_range_handler),
+			                  manager);
+
+		manager->priv->buffer_signals[BEGIN_USER_ACTION] =
+			g_signal_connect (buffer,
+			                  "begin-user-action",
+			                  G_CALLBACK (begin_user_action_handler),
+			                  manager);
+
+		manager->priv->buffer_signals[MODIFIED_CHANGED] =
+			g_signal_connect (buffer,
+			                  "modified-changed",
+			                  G_CALLBACK (modified_changed_handler),
+			                  manager);
+	}
+}
+
+static void
+set_max_undo_levels (GtkSourceUndoManagerDefault *manager,
+                     gint                         max_undo_levels)
+{
+	gint old_levels;
+
+	old_levels = manager->priv->max_undo_levels;
+	manager->priv->max_undo_levels = max_undo_levels;
+
+	if (max_undo_levels < 1)
+		return;
+
+	if (old_levels > max_undo_levels)
+	{
+		/* strip redo actions first */
+		while (manager->priv->next_redo >= 0 &&
+		       (manager->priv->num_of_groups > max_undo_levels))
+		{
+			free_first_n_actions (manager, 1);
+			manager->priv->next_redo--;
+		}
+
+		/* now remove undo actions if necessary */
+		check_list_size (manager);
+
+		/* emit "can_undo" and/or "can_redo" if appropiate */
+		if (manager->priv->next_redo < 0 && manager->priv->can_redo)
+		{
+			manager->priv->can_redo = FALSE;
+			gtk_source_undo_manager_can_redo_changed (GTK_SOURCE_UNDO_MANAGER (manager));
+		}
+
+		if (manager->priv->can_undo &&
+		    manager->priv->next_redo >= (gint)manager->priv->actions->len - 1)
+		{
+			manager->priv->can_undo = FALSE;
+			gtk_source_undo_manager_can_undo_changed (GTK_SOURCE_UNDO_MANAGER (manager));
+		}
+	}
+}
+
+static void
+gtk_source_undo_manager_default_dispose (GObject *object)
+{
+	GtkSourceUndoManagerDefault *manager;
+
+	manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (object);
+
+	if (manager->priv->buffer != NULL)
+	{
+		/* Clear the buffer */
+		set_buffer (manager, NULL);
+	}
+}
+
+static void
+gtk_source_undo_manager_default_set_property (GObject      *object,
+                                              guint         prop_id,
+                                              const GValue *value,
+                                              GParamSpec   *pspec)
+{
+	GtkSourceUndoManagerDefault *self = GTK_SOURCE_UNDO_MANAGER_DEFAULT (object);
+	
+	switch (prop_id)
+	{
+		case PROP_BUFFER:
+			set_buffer (self, g_value_get_object (value));
+		break;
+		case PROP_MAX_UNDO_LEVELS:
+			gtk_source_undo_manager_default_set_max_undo_levels (self,
+			                                                     g_value_get_int (value));
+		break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+gtk_source_undo_manager_default_get_property (GObject    *object,
+                                              guint       prop_id,
+                                              GValue     *value,
+                                              GParamSpec *pspec)
+{
+	GtkSourceUndoManagerDefault *self = GTK_SOURCE_UNDO_MANAGER_DEFAULT (object);
+	
+	switch (prop_id)
+	{
+		case PROP_BUFFER:
+			g_value_set_object (value, self->priv->buffer);
+		break;
+		case PROP_MAX_UNDO_LEVELS:
+			g_value_set_int (value, self->priv->max_undo_levels);
+		break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+gtk_source_undo_manager_default_class_init (GtkSourceUndoManagerDefaultClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->finalize = gtk_source_undo_manager_default_finalize;
+	object_class->dispose = gtk_source_undo_manager_default_dispose;
+
+	object_class->set_property = gtk_source_undo_manager_default_set_property;
+	object_class->get_property = gtk_source_undo_manager_default_get_property;
+
+	g_object_class_install_property (object_class,
+	                                 PROP_BUFFER,
+	                                 g_param_spec_object ("buffer",
+	                                                      _("Buffer"),
+	                                                      _("The text buffer to add undo support on"),
+	                                                      GTK_TYPE_TEXT_BUFFER,
+	                                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	g_object_class_install_property (object_class,
+	                                 PROP_MAX_UNDO_LEVELS,
+	                                 g_param_spec_int ("max-undo-levels",
+	                                                   _("Maximum Undo Levels"),
+	                                                   _("Number of undo levels for "
+							     "the buffer"),
+	                                                   -1,
+	                                                   G_MAXINT,
+	                                                   DEFAULT_MAX_UNDO_LEVELS,
+	                                                   G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+	g_type_class_add_private (object_class, sizeof(GtkSourceUndoManagerDefaultPrivate));
+}
+
+static void
+gtk_source_undo_manager_default_init (GtkSourceUndoManagerDefault *um)
+{
+	um->priv = G_TYPE_INSTANCE_GET_PRIVATE (um, GTK_TYPE_SOURCE_UNDO_MANAGER_DEFAULT,
+	                                        GtkSourceUndoManagerDefaultPrivate);
+
+	um->priv->actions = g_ptr_array_new ();
+}
+
+static void
+end_not_undoable_action_internal (GtkSourceUndoManagerDefault *manager)
+{
+	g_return_if_fail (manager->priv->running_not_undoable_actions > 0);
+
+	--manager->priv->running_not_undoable_actions;
+}
+
+/* Interface implementations */
+static void
+gtk_source_undo_manager_begin_not_undoable_action_impl (GtkSourceUndoManager *manager)
+{
+	GtkSourceUndoManagerDefault *manager_default;
+
+	manager_default = GTK_SOURCE_UNDO_MANAGER_DEFAULT (manager);
+	++manager_default->priv->running_not_undoable_actions;
+}
+
+static void
+gtk_source_undo_manager_end_not_undoable_action_impl (GtkSourceUndoManager *manager)
+{
+	GtkSourceUndoManagerDefault *manager_default;
+
+	manager_default = GTK_SOURCE_UNDO_MANAGER_DEFAULT (manager);
+	end_not_undoable_action_internal (manager_default);
+
+	if (manager_default->priv->running_not_undoable_actions == 0)
+	{
+		clear_undo (manager_default);
+	}
+}
+
+static gboolean
+gtk_source_undo_manager_can_undo_impl (GtkSourceUndoManager *manager)
+{
+	return GTK_SOURCE_UNDO_MANAGER_DEFAULT (manager)->priv->can_undo;
+}
+
+static gboolean
+gtk_source_undo_manager_can_redo_impl (GtkSourceUndoManager *manager)
+{
+	return GTK_SOURCE_UNDO_MANAGER_DEFAULT (manager)->priv->can_redo;
+}
+
+static void
+set_cursor (GtkTextBuffer *buffer,
+            gint           cursor)
+{
+	GtkTextIter iter;
+
+	/* Place the cursor at the requested position */
+	gtk_text_buffer_get_iter_at_offset (buffer, &iter, cursor);
+	gtk_text_buffer_place_cursor (buffer, &iter);
+}
+
+static void
+insert_text (GtkTextBuffer *buffer,
+             gint           pos,
+             const gchar   *text,
+             gint           len)
+{
+	GtkTextIter iter;
+
+	gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos);
+	gtk_text_buffer_insert (buffer, &iter, text, len);
+}
+
+static void
+delete_text (GtkTextBuffer *buffer,
+             gint           start,
+             gint           end)
+{
+	GtkTextIter start_iter;
+	GtkTextIter end_iter;
+
+	gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
+
+	if (end < 0)
+		gtk_text_buffer_get_end_iter (buffer, &end_iter);
+	else
+		gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
+
+	gtk_text_buffer_delete (buffer, &start_iter, &end_iter);
+}
+
+static gchar *
+get_chars (GtkTextBuffer *buffer,
+           gint           start,
+           gint           end)
+{
+	GtkTextIter start_iter;
+	GtkTextIter end_iter;
+
+	gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
+
+	if (end < 0)
+		gtk_text_buffer_get_end_iter (buffer, &end_iter);
+	else
+		gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
+
+	return gtk_text_buffer_get_slice (buffer, &start_iter, &end_iter, TRUE);
+}
+
+static GtkSourceUndoAction *
+action_list_nth_data (GPtrArray *array,
+                      gint       n)
+{
+	if (n < 0 || n >= (gint)array->len)
+		return NULL;
+	else
+		return array->pdata[array->len - 1 - n];
+}
+
+static void
+action_list_prepend (GPtrArray           *array,
+                     GtkSourceUndoAction *action)
+{
+	g_ptr_array_add (array, action);
+}
+
+static GtkSourceUndoAction *
+action_list_last_data (GPtrArray *array)
+{
+	if (array->len != 0)
+		return array->pdata[0];
+	else
+		return NULL;
+}
+
+static void
+action_list_delete_last (GPtrArray *array)
+{
+	if (array->len != 0)
+	{
+		memmove (&array->pdata[0], &array->pdata[1], (array->len - 1)*sizeof (gpointer));
+		g_ptr_array_set_size (array, array->len - 1);
+	}
+}
+
+static void
+gtk_source_undo_manager_undo_impl (GtkSourceUndoManager *manager)
+{
+	GtkSourceUndoManagerDefault *manager_default;
+	GtkSourceUndoAction *undo_action;
+	gboolean modified = FALSE;
+	gint cursor_pos = -1;
+
+	manager_default = GTK_SOURCE_UNDO_MANAGER_DEFAULT (manager);
+
+	g_return_if_fail (manager_default->priv->can_undo);
+
+	manager_default->priv->modified_undoing_group = FALSE;
+
+	gtk_source_undo_manager_begin_not_undoable_action (manager);
+
+	do
+	{
+		undo_action = action_list_nth_data (manager_default->priv->actions,
+		                                    manager_default->priv->next_redo + 1);
+
+		g_return_if_fail (undo_action != NULL);
+
+		/* undo_action->modified can be TRUE only if undo_action->order_in_group <= 1 */
+		g_return_if_fail ((undo_action->order_in_group <= 1) ||
+				  ((undo_action->order_in_group > 1) && !undo_action->modified));
+
+		if (undo_action->order_in_group <= 1)
+		{
+			/* Set modified to TRUE only if the buffer did not change its state from
+			 * "not modified" to "modified" undoing an action (with order_in_group > 1)
+			 * in current group. */
+			modified = (undo_action->modified &&
+			            !manager_default->priv->modified_undoing_group);
+		}
+
+		switch (undo_action->action_type)
+		{
+			case GTK_SOURCE_UNDO_ACTION_DELETE:
+				insert_text (manager_default->priv->buffer,
+				             undo_action->action.delete.start,
+				             undo_action->action.delete.text,
+				             strlen (undo_action->action.delete.text));
+
+				if (undo_action->action.delete.forward)
+					cursor_pos = undo_action->action.delete.start;
+				else
+					cursor_pos = undo_action->action.delete.end;
+
+				break;
+
+			case GTK_SOURCE_UNDO_ACTION_INSERT:
+				delete_text (manager_default->priv->buffer,
+				             undo_action->action.insert.pos,
+				             undo_action->action.insert.pos +
+				             undo_action->action.insert.chars);
+
+				cursor_pos = undo_action->action.insert.pos;
+				break;
+
+			default:
+				/* Unknown action type. */
+				g_return_if_reached ();
+		}
+
+		++manager_default->priv->next_redo;
+
+	} while (undo_action->order_in_group > 1);
+
+	if (cursor_pos >= 0)
+		set_cursor (manager_default->priv->buffer, cursor_pos);
+
+	if (modified)
+	{
+		--manager_default->priv->next_redo;
+		gtk_text_buffer_set_modified (manager_default->priv->buffer, FALSE);
+		++manager_default->priv->next_redo;
+	}
+
+	/* FIXME: why does this call the internal one ? */
+	end_not_undoable_action_internal (manager_default);
+
+	manager_default->priv->modified_undoing_group = FALSE;
+
+	if (!manager_default->priv->can_redo)
+	{
+		manager_default->priv->can_redo = TRUE;
+		gtk_source_undo_manager_can_redo_changed (manager);
+	}
+
+	if (manager_default->priv->next_redo >= (gint)manager_default->priv->actions->len - 1)
+	{
+		manager_default->priv->can_undo = FALSE;
+		gtk_source_undo_manager_can_undo_changed (manager);
+	}
+}
+
+static void
+gtk_source_undo_manager_redo_impl (GtkSourceUndoManager *manager)
+{
+	GtkSourceUndoManagerDefault *manager_default;
+	GtkSourceUndoAction *undo_action;
+	gboolean modified = FALSE;
+	gint cursor_pos = -1;
+
+	manager_default = GTK_SOURCE_UNDO_MANAGER_DEFAULT (manager);
+
+	g_return_if_fail (manager_default->priv->can_redo);
+
+	undo_action = action_list_nth_data (manager_default->priv->actions,
+	                                    manager_default->priv->next_redo);
+	g_return_if_fail (undo_action != NULL);
+
+	gtk_source_undo_manager_begin_not_undoable_action (manager);
+
+	do
+	{
+		if (undo_action->modified)
+		{
+			g_return_if_fail (undo_action->order_in_group <= 1);
+			modified = TRUE;
+		}
+
+		--manager_default->priv->next_redo;
+
+		switch (undo_action->action_type)
+		{
+			case GTK_SOURCE_UNDO_ACTION_DELETE:
+				delete_text (manager_default->priv->buffer,
+				             undo_action->action.delete.start,
+				             undo_action->action.delete.end);
+
+				cursor_pos = undo_action->action.delete.start;
+
+				break;
+
+			case GTK_SOURCE_UNDO_ACTION_INSERT:
+				cursor_pos = undo_action->action.insert.pos +
+				             undo_action->action.insert.length;
+
+				insert_text (manager_default->priv->buffer,
+				             undo_action->action.insert.pos,
+				             undo_action->action.insert.text,
+				             undo_action->action.insert.length);
+
+				break;
+
+			default:
+				/* Unknown action type */
+				++manager_default->priv->next_redo;
+				g_return_if_reached ();
+		}
+
+		if (manager_default->priv->next_redo < 0)
+			undo_action = NULL;
+		else
+			undo_action = action_list_nth_data (manager_default->priv->actions,
+			                                    manager_default->priv->next_redo);
+
+	} while ((undo_action != NULL) && (undo_action->order_in_group > 1));
+
+	if (cursor_pos >= 0)
+		set_cursor (manager_default->priv->buffer, cursor_pos);
+
+	if (modified)
+	{
+		++manager_default->priv->next_redo;
+		gtk_text_buffer_set_modified (manager_default->priv->buffer, FALSE);
+		--manager_default->priv->next_redo;
+	}
+
+	/* FIXME: why is this only internal ?*/
+	end_not_undoable_action_internal (manager_default);
+
+	if (manager_default->priv->next_redo < 0)
+	{
+		manager_default->priv->can_redo = FALSE;
+		gtk_source_undo_manager_can_redo_changed (manager);
+	}
+
+	if (!manager_default->priv->can_undo)
+	{
+		manager_default->priv->can_undo = TRUE;
+		gtk_source_undo_manager_can_undo_changed (manager);
+	}
+}
+
+static void
+gtk_source_undo_action_free (GtkSourceUndoAction *action)
+{
+	if (action == NULL)
+		return;
+
+	if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
+		g_free (action->action.insert.text);
+	else if (action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
+		g_free (action->action.delete.text);
+	else
+		g_return_if_reached ();
+
+	g_free (action);
+}
+
+static void
+free_action_list (GtkSourceUndoManagerDefault *um)
+{
+	gint i;
+
+	for (i = (gint)um->priv->actions->len - 1; i >= 0; i--)
+	{
+		GtkSourceUndoAction *action = um->priv->actions->pdata[i];
+
+		if (action->order_in_group == 1)
+			--um->priv->num_of_groups;
+
+		if (action->modified)
+			um->priv->modified_action = INVALID;
+
+		gtk_source_undo_action_free (action);
+	}
+
+	/* Some arbitrary limit, to avoid wasting space */
+	if (um->priv->actions->len > 2048)
+	{
+		g_ptr_array_free (um->priv->actions, TRUE);
+		um->priv->actions = g_ptr_array_new ();
+	}
+	else
+	{
+		g_ptr_array_set_size (um->priv->actions, 0);
+	}
+}
+
+static void
+insert_text_handler (GtkTextBuffer               *buffer,
+                     GtkTextIter                 *pos,
+                     const gchar                 *text,
+                     gint                         length,
+                     GtkSourceUndoManagerDefault *manager)
+{
+	GtkSourceUndoAction undo_action;
+
+	if (manager->priv->running_not_undoable_actions > 0)
+		return;
+
+	undo_action.action_type = GTK_SOURCE_UNDO_ACTION_INSERT;
+
+	undo_action.action.insert.pos    = gtk_text_iter_get_offset (pos);
+	undo_action.action.insert.text   = (gchar*) text;
+	undo_action.action.insert.length = length;
+	undo_action.action.insert.chars  = g_utf8_strlen (text, length);
+
+	if ((undo_action.action.insert.chars > 1) || (g_utf8_get_char (text) == '\n'))
+
+		undo_action.mergeable = FALSE;
+	else
+		undo_action.mergeable = TRUE;
+
+	undo_action.modified = FALSE;
+
+	add_action (manager, &undo_action);
+}
+
+static void
+delete_range_handler (GtkTextBuffer               *buffer,
+                      GtkTextIter                 *start,
+                      GtkTextIter                 *end,
+                      GtkSourceUndoManagerDefault *um)
+{
+	GtkSourceUndoAction undo_action;
+	GtkTextIter insert_iter;
+
+	if (um->priv->running_not_undoable_actions > 0)
+		return;
+
+	undo_action.action_type = GTK_SOURCE_UNDO_ACTION_DELETE;
+
+	gtk_text_iter_order (start, end);
+
+	undo_action.action.delete.start  = gtk_text_iter_get_offset (start);
+	undo_action.action.delete.end    = gtk_text_iter_get_offset (end);
+
+	undo_action.action.delete.text   = get_chars (
+						buffer,
+						undo_action.action.delete.start,
+						undo_action.action.delete.end);
+
+	/* figure out if the user used the Delete or the Backspace key */
+	gtk_text_buffer_get_iter_at_mark (buffer, &insert_iter,
+					  gtk_text_buffer_get_insert (buffer));
+	if (gtk_text_iter_get_offset (&insert_iter) <= undo_action.action.delete.start)
+		undo_action.action.delete.forward = TRUE;
+	else
+		undo_action.action.delete.forward = FALSE;
+
+	if (((undo_action.action.delete.end - undo_action.action.delete.start) > 1) ||
+	     (g_utf8_get_char (undo_action.action.delete.text  ) == '\n'))
+		undo_action.mergeable = FALSE;
+	else
+		undo_action.mergeable = TRUE;
+
+	undo_action.modified = FALSE;
+
+	add_action (um, &undo_action);
+
+	g_free (undo_action.action.delete.text);
+
+}
+
+static void
+begin_user_action_handler (GtkTextBuffer               *buffer,
+                           GtkSourceUndoManagerDefault *um)
+{
+	if (um->priv->running_not_undoable_actions > 0)
+		return;
+
+	um->priv->actions_in_current_group = 0;
+}
+
+static void
+add_action (GtkSourceUndoManagerDefault *um,
+            const GtkSourceUndoAction   *undo_action)
+{
+	GtkSourceUndoAction* action;
+
+	if (um->priv->next_redo >= 0)
+	{
+		free_first_n_actions (um, um->priv->next_redo + 1);
+	}
+
+	um->priv->next_redo = -1;
+
+	if (!merge_action (um, undo_action))
+	{
+		action = g_new (GtkSourceUndoAction, 1);
+		*action = *undo_action;
+
+		if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
+			action->action.insert.text = g_strndup (undo_action->action.insert.text, undo_action->action.insert.length);
+		else if (action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
+			action->action.delete.text = g_strdup (undo_action->action.delete.text);
+		else
+		{
+			g_free (action);
+			g_return_if_reached ();
+		}
+
+		++um->priv->actions_in_current_group;
+		action->order_in_group = um->priv->actions_in_current_group;
+
+		if (action->order_in_group == 1)
+			++um->priv->num_of_groups;
+
+		action_list_prepend (um->priv->actions, action);
+	}
+
+	check_list_size (um);
+
+	if (!um->priv->can_undo)
+	{
+		um->priv->can_undo = TRUE;
+		gtk_source_undo_manager_can_undo_changed (GTK_SOURCE_UNDO_MANAGER (um));
+	}
+
+	if (um->priv->can_redo)
+	{
+		um->priv->can_redo = FALSE;
+		gtk_source_undo_manager_can_redo_changed (GTK_SOURCE_UNDO_MANAGER (um));
+	}
+}
+
+static void
+free_first_n_actions (GtkSourceUndoManagerDefault *um,
+                      gint                         n)
+{
+	gint i;
+
+	if (um->priv->actions->len == 0)
+		return;
+
+	for (i = 0; i < n; i++)
+	{
+		GtkSourceUndoAction *action = um->priv->actions->pdata[um->priv->actions->len - 1];
+
+		if (action->order_in_group == 1)
+			--um->priv->num_of_groups;
+
+		if (action->modified)
+			um->priv->modified_action = INVALID;
+
+		gtk_source_undo_action_free (action);
+
+		g_ptr_array_set_size (um->priv->actions, um->priv->actions->len - 1);
+
+		if (um->priv->actions->len == 0)
+			return;
+	}
+}
+
+static void
+check_list_size (GtkSourceUndoManagerDefault *um)
+{
+	gint undo_levels;
+
+	undo_levels = um->priv->max_undo_levels;
+
+	if (undo_levels < 1)
+		return;
+
+	if (um->priv->num_of_groups > undo_levels)
+	{
+		GtkSourceUndoAction *undo_action;
+
+		undo_action = action_list_last_data (um->priv->actions);
+
+		do
+		{
+			if (undo_action->order_in_group == 1)
+				--um->priv->num_of_groups;
+
+			if (undo_action->modified)
+				um->priv->modified_action = INVALID;
+
+			gtk_source_undo_action_free (undo_action);
+
+			action_list_delete_last (um->priv->actions);
+
+			undo_action = action_list_last_data (um->priv->actions);
+			g_return_if_fail (undo_action != NULL);
+
+		} while ((undo_action->order_in_group > 1) ||
+			 (um->priv->num_of_groups > undo_levels));
+	}
+}
+
+/**
+ * gtk_source_undo_manager_default_merge_action:
+ * @um: a #GtkSourceUndoManagerDefault.
+ * @undo_action: a #GtkSourceUndoAction.
+ *
+ * This function tries to merge the undo action at the top of
+ * the stack with a new undo action. So when we undo for example
+ * typing, we can undo the whole word and not each letter by itself.
+ *
+ * Return Value: %TRUE is merge was sucessful, %FALSE otherwise.
+ **/
+static gboolean
+merge_action (GtkSourceUndoManagerDefault *um,
+              const GtkSourceUndoAction   *undo_action)
+{
+	GtkSourceUndoAction *last_action;
+
+	g_return_val_if_fail (GTK_IS_SOURCE_UNDO_MANAGER_DEFAULT (um), FALSE);
+	g_return_val_if_fail (um->priv != NULL, FALSE);
+
+	if (um->priv->actions->len == 0)
+		return FALSE;
+
+	last_action = action_list_nth_data (um->priv->actions, 0);
+
+	if (!last_action->mergeable)
+		return FALSE;
+
+	if ((!undo_action->mergeable) ||
+	    (undo_action->action_type != last_action->action_type))
+	{
+		last_action->mergeable = FALSE;
+		return FALSE;
+	}
+
+	if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
+	{
+		if ((last_action->action.delete.forward != undo_action->action.delete.forward) ||
+		    ((last_action->action.delete.start != undo_action->action.delete.start) &&
+		     (last_action->action.delete.start != undo_action->action.delete.end)))
+		{
+			last_action->mergeable = FALSE;
+			return FALSE;
+		}
+
+		if (last_action->action.delete.start == undo_action->action.delete.start)
+		{
+			gchar *str;
+
+#define L  (last_action->action.delete.end - last_action->action.delete.start - 1)
+#define g_utf8_get_char_at(p,i) g_utf8_get_char(g_utf8_offset_to_pointer((p),(i)))
+
+			/* Deleted with the delete key */
+			if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') &&
+			    (g_utf8_get_char (undo_action->action.delete.text) != '\t') &&
+                            ((g_utf8_get_char_at (last_action->action.delete.text, L) == ' ') ||
+			     (g_utf8_get_char_at (last_action->action.delete.text, L)  == '\t')))
+			{
+				last_action->mergeable = FALSE;
+				return FALSE;
+			}
+
+			str = g_strdup_printf ("%s%s", last_action->action.delete.text,
+				undo_action->action.delete.text);
+
+			g_free (last_action->action.delete.text);
+			last_action->action.delete.end += (undo_action->action.delete.end -
+							   undo_action->action.delete.start);
+			last_action->action.delete.text = str;
+		}
+		else
+		{
+			gchar *str;
+
+			/* Deleted with the backspace key */
+			if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') &&
+			    (g_utf8_get_char (undo_action->action.delete.text) != '\t') &&
+                            ((g_utf8_get_char (last_action->action.delete.text) == ' ') ||
+			     (g_utf8_get_char (last_action->action.delete.text) == '\t')))
+			{
+				last_action->mergeable = FALSE;
+				return FALSE;
+			}
+
+			str = g_strdup_printf ("%s%s", undo_action->action.delete.text,
+				last_action->action.delete.text);
+
+			g_free (last_action->action.delete.text);
+			last_action->action.delete.start = undo_action->action.delete.start;
+			last_action->action.delete.text = str;
+		}
+	}
+	else if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
+	{
+		gchar* str;
+
+#define I (last_action->action.insert.chars - 1)
+
+		if ((undo_action->action.insert.pos !=
+		     	(last_action->action.insert.pos + last_action->action.insert.chars)) ||
+		    ((g_utf8_get_char (undo_action->action.insert.text) != ' ') &&
+		      (g_utf8_get_char (undo_action->action.insert.text) != '\t') &&
+		     ((g_utf8_get_char_at (last_action->action.insert.text, I) == ' ') ||
+		      (g_utf8_get_char_at (last_action->action.insert.text, I) == '\t')))
+		   )
+		{
+			last_action->mergeable = FALSE;
+			return FALSE;
+		}
+
+		str = g_strdup_printf ("%s%s", last_action->action.insert.text,
+				undo_action->action.insert.text);
+
+		g_free (last_action->action.insert.text);
+		last_action->action.insert.length += undo_action->action.insert.length;
+		last_action->action.insert.text = str;
+		last_action->action.insert.chars += undo_action->action.insert.chars;
+
+	}
+	else
+		/* Unknown action inside undo merge encountered */
+		g_return_val_if_reached (TRUE);
+
+	return TRUE;
+}
+
+static void
+modified_changed_handler (GtkTextBuffer               *buffer,
+                          GtkSourceUndoManagerDefault *manager)
+{
+	GtkSourceUndoAction *action;
+	gint idx;
+
+	if (manager->priv->actions->len == 0)
+		return;
+
+	idx = manager->priv->next_redo + 1;
+	action = action_list_nth_data (manager->priv->actions, idx);
+
+	if (gtk_text_buffer_get_modified (buffer) == FALSE)
+	{
+		if (action != NULL)
+			action->mergeable = FALSE;
+
+		if (manager->priv->modified_action != NULL)
+		{
+			if (manager->priv->modified_action != INVALID)
+				manager->priv->modified_action->modified = FALSE;
+
+			manager->priv->modified_action = NULL;
+		}
+
+		return;
+	}
+
+	if (action == NULL)
+	{
+		g_return_if_fail (manager->priv->running_not_undoable_actions > 0);
+
+		return;
+	}
+
+	if (manager->priv->modified_action != NULL)
+	{
+		g_message ("%s: oops", G_STRLOC);
+		return;
+	}
+
+	if (action->order_in_group > 1)
+		manager->priv->modified_undoing_group  = TRUE;
+
+	while (action->order_in_group > 1)
+	{
+		action = action_list_nth_data (manager->priv->actions, ++idx);
+		g_return_if_fail (action != NULL);
+	}
+
+	action->modified = TRUE;
+	manager->priv->modified_action = action;
+}
+
+static void
+gtk_source_undo_manager_iface_init (GtkSourceUndoManagerIface *iface)
+{
+	iface->can_undo = gtk_source_undo_manager_can_undo_impl;
+	iface->can_redo = gtk_source_undo_manager_can_redo_impl;
+
+	iface->undo = gtk_source_undo_manager_undo_impl;
+	iface->redo = gtk_source_undo_manager_redo_impl;
+
+	iface->begin_not_undoable_action = gtk_source_undo_manager_begin_not_undoable_action_impl;
+	iface->end_not_undoable_action = gtk_source_undo_manager_end_not_undoable_action_impl;
+}
+
+void
+gtk_source_undo_manager_default_set_max_undo_levels (GtkSourceUndoManagerDefault *manager,
+                                                     gint                         max_undo_levels)
+{
+	g_return_if_fail (GTK_IS_SOURCE_UNDO_MANAGER_DEFAULT (manager));
+
+	set_max_undo_levels (manager, max_undo_levels);
+	g_object_notify (G_OBJECT (manager), "max-undo-levels");
+}
diff --git a/gtksourceview/gtksourceundomanagerdefault.h b/gtksourceview/gtksourceundomanagerdefault.h
new file mode 100644
index 0000000..b714e66
--- /dev/null
+++ b/gtksourceview/gtksourceundomanagerdefault.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * gtksourceundomanager_defaultdefault.h
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
+ * Copyright (C) 2002, 2003 Paolo Maggi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __GTK_SOURCE_UNDO_MANAGER_DEFAULT_H__
+#define __GTK_SOURCE_UNDO_MANAGER_DEFAULT_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_SOURCE_UNDO_MANAGER_DEFAULT		(gtk_source_undo_manager_default_get_type ())
+#define GTK_SOURCE_UNDO_MANAGER_DEFAULT(obj)		(G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_SOURCE_UNDO_MANAGER_DEFAULT, GtkSourceUndoManagerDefault))
+#define GTK_SOURCE_UNDO_MANAGER_DEFAULT_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_SOURCE_UNDO_MANAGER_DEFAULT, GtkSourceUndoManagerDefaultClass))
+#define GTK_IS_SOURCE_UNDO_MANAGER_DEFAULT(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SOURCE_UNDO_MANAGER_DEFAULT))
+#define GTK_IS_SOURCE_UNDO_MANAGER_DEFAULT_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SOURCE_UNDO_MANAGER_DEFAULT))
+#define GTK_SOURCE_UNDO_MANAGER_DEFAULT_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SOURCE_UNDO_MANAGER_DEFAULT, GtkSourceUndoManagerDefaultClass))
+
+typedef struct _GtkSourceUndoManagerDefault        	GtkSourceUndoManagerDefault;
+typedef struct _GtkSourceUndoManagerDefaultClass 	GtkSourceUndoManagerDefaultClass;
+typedef struct _GtkSourceUndoManagerDefaultPrivate 	GtkSourceUndoManagerDefaultPrivate;
+
+struct _GtkSourceUndoManagerDefault
+{
+	GObject parent;
+
+	GtkSourceUndoManagerDefaultPrivate *priv;
+};
+
+struct _GtkSourceUndoManagerDefaultClass
+{
+	GObjectClass parent_class;
+};
+
+GType gtk_source_undo_manager_default_get_type (void) G_GNUC_CONST;
+
+void gtk_source_undo_manager_default_set_max_undo_levels (GtkSourceUndoManagerDefault *manager,
+                                                          gint                         max_undo_levels);
+
+G_END_DECLS
+
+#endif /* __GTK_SOURCE_UNDO_MANAGER_DEFAULT_H__ */
+
+



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