Fix crash with auto-indent
[claws.git] / src / undo.c
index f14c9e4707070c891d8584cfa590bdae9c7881db..61c4573404d907cd81ccc388bf390e22f63424a7 100644 (file)
@@ -1,10 +1,10 @@
 /*
  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
- * Copyright (C) 1999-2001 Hiroyuki Yamamoto
+ * Copyright (C) 1999-2012 Hiroyuki Yamamoto and the Claws Mail team
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
+ * the Free Software Foundation; either version 3 of the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
@@ -13,8 +13,8 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * 
  */
 
 /* code ported from gedit */
@@ -22,6 +22,7 @@
 
 #ifdef HAVE_CONFIG_H
 #  include "config.h"
+#include "claws-features.h"
 #endif
 
 #include <glib.h>
@@ -32,7 +33,6 @@
 #include "undo.h"
 #include "utils.h"
 #include "prefs_common.h"
-#include "gtkstext.h"
 
 typedef struct _UndoInfo UndoInfo;
 
@@ -46,6 +46,15 @@ struct _UndoInfo
        gint mergeable;
 };
 
+struct _UndoWrap
+{
+       gint lock;
+       gchar *pre_wrap_content;
+       gint start_pos;
+       gint end_pos;
+       gint len_change;
+};
+
 static void undo_free_list     (GList         **list_pointer);
 static void undo_check_size    (UndoMain       *undostruct);
 static gint undo_merge         (GList          *list,
@@ -58,20 +67,20 @@ static void undo_add                (const gchar    *text,
                                 gint            end_pos,
                                 UndoAction      action,
                                 UndoMain       *undostruct);
-static gint undo_get_selection (GtkEditable    *text,
+static gint undo_get_selection (GtkTextView    *textview,
                                 guint          *start,
                                 guint          *end);
-static void undo_insert_text_cb        (GtkEditable    *editable,
+static void undo_insert_text_cb (GtkTextBuffer *textbuf,
+                                GtkTextIter    *iter,
                                 gchar          *new_text,
-                                gint            new_text_length,
-                                gint           *position,
+                                gint           new_text_length,
                                 UndoMain       *undostruct);
-static void undo_delete_text_cb        (GtkEditable    *editable,
-                                gint            start_pos,
-                                gint            end_pos,
+static void undo_delete_text_cb (GtkTextBuffer *textbuf,
+                                GtkTextIter    *start,
+                                GtkTextIter    *end,
                                 UndoMain       *undostruct);
 
-static void undo_paste_clipboard_cb    (GtkEditable    *editable,
+static void undo_paste_clipboard_cb    (GtkTextView    *textview,
                                         UndoMain       *undostruct);
 
 void undo_undo                 (UndoMain       *undostruct);
@@ -81,23 +90,25 @@ void undo_redo                      (UndoMain       *undostruct);
 UndoMain *undo_init(GtkWidget *text) 
 {
        UndoMain *undostruct;
-       
-       g_return_val_if_fail(text != NULL, NULL);
+       GtkTextView *textview = GTK_TEXT_VIEW(text); 
+       GtkTextBuffer *textbuf = gtk_text_view_get_buffer(textview);
+
+       cm_return_val_if_fail(text != NULL, NULL);
 
-       undostruct = g_new(UndoMain, 1);
-       undostruct->text = text;
+       undostruct = g_new0(UndoMain, 1);
+       undostruct->textview = textview;
        undostruct->undo = NULL;
        undostruct->redo = NULL;
        undostruct->paste = 0;
        undostruct->undo_state = FALSE;
        undostruct->redo_state = FALSE;
 
-       gtk_signal_connect(GTK_OBJECT(text), "insert-text",
-                          GTK_SIGNAL_FUNC(undo_insert_text_cb), undostruct);
-       gtk_signal_connect(GTK_OBJECT(text), "delete-text",
-                          GTK_SIGNAL_FUNC(undo_delete_text_cb), undostruct);
-       gtk_signal_connect(GTK_OBJECT(text), "paste-clipboard",
-                          GTK_SIGNAL_FUNC(undo_paste_clipboard_cb), undostruct);
+       g_signal_connect(G_OBJECT(textbuf), "insert-text",
+                        G_CALLBACK(undo_insert_text_cb), undostruct);
+       g_signal_connect(G_OBJECT(textbuf), "delete-range",
+                        G_CALLBACK(undo_delete_text_cb), undostruct);
+       g_signal_connect(G_OBJECT(textview), "paste-clipboard",
+                        G_CALLBACK(undo_paste_clipboard_cb), undostruct);
 
        return undostruct;
 }
@@ -153,7 +164,7 @@ static void undo_free_list(GList **list_pointer)
 void undo_set_change_state_func(UndoMain *undostruct, UndoChangeStateFunc func,
                                gpointer data)
 {
-       g_return_if_fail(undostruct != NULL);
+       cm_return_if_fail(undostruct != NULL);
 
        undostruct->change_state_func = func;
        undostruct->change_state_data = data;
@@ -182,7 +193,6 @@ static void undo_check_size(UndoMain *undostruct)
                undostruct->undo = g_list_remove(undostruct->undo, last_undo);
                undo_object_free(last_undo);
        }
-       debug_print("g_list_length(undostruct->undo): %d\n", length);
 }
 
 /**
@@ -233,40 +243,23 @@ static gint undo_merge(GList *list, guint start_pos, guint end_pos,
        }
 
        if (action == UNDO_ACTION_DELETE) {
-               gboolean checkit = TRUE;
-
                if (last_undo->start_pos != end_pos &&
                    last_undo->start_pos != start_pos) {
                        last_undo->mergeable = FALSE;
                        return FALSE;
                } else if (last_undo->start_pos == start_pos) {
                        /* Deleted with the delete key */
-                       if (text[0] != ' ' && text[0] != '\t' &&
-                           (last_undo->text[last_undo->end_pos - last_undo->start_pos - 1] == ' ' ||
-                            last_undo->text[last_undo->end_pos - last_undo->start_pos - 1] == '\t'))
-                               checkit = FALSE;
-
                        temp_string = g_strdup_printf("%s%s", last_undo->text, text);
                        last_undo->end_pos++;
                        g_free(last_undo->text);
                        last_undo->text = temp_string;
                } else {
                        /* Deleted with the backspace key */
-                       if (text[0] != ' ' && text[0] != '\t' &&
-                           (last_undo->text[0] == ' ' ||
-                            last_undo->text[0] == '\t'))
-                               checkit = FALSE;
-
                        temp_string = g_strdup_printf("%s%s", text, last_undo->text);
                        last_undo->start_pos = start_pos;
                        g_free(last_undo->text);
                        last_undo->text = temp_string;
                }
-
-               if (!checkit) {
-                       last_undo->mergeable = FALSE;
-                       return FALSE;
-               }
        } else if (action == UNDO_ACTION_INSERT) {
                if (last_undo->end_pos != start_pos) {
                        last_undo->mergeable = FALSE;
@@ -280,7 +273,6 @@ static gint undo_merge(GList *list, guint start_pos, guint end_pos,
        } else
                debug_print("Unknown action [%i] inside undo merge encountered", action);
 
-       debug_print("Merged: %s\n", text);
        return TRUE;
 }
 
@@ -302,11 +294,10 @@ static void undo_add(const gchar *text,
                     UndoAction action, UndoMain *undostruct) 
 {
        UndoInfo *undoinfo;
+       GtkAdjustment *vadj;
 
-       debug_print("undo_add(%i)*%s*\n", strlen (text), text);
-
-       g_return_if_fail(text != NULL);
-       g_return_if_fail(end_pos >= start_pos);
+       cm_return_if_fail(text != NULL);
+       cm_return_if_fail(end_pos >= start_pos);
 
        undo_free_list(&undostruct->redo);
 
@@ -316,7 +307,7 @@ static void undo_add(const gchar *text,
                                      undostruct->change_state_data);
 
        if (undostruct->paste != 0) {
-               if (action == UNDO_ACTION_INSERT) 
+               if (action == UNDO_ACTION_INSERT)
                        action = UNDO_ACTION_REPLACE_INSERT;
                else 
                        action = UNDO_ACTION_REPLACE_DELETE;
@@ -330,10 +321,10 @@ static void undo_add(const gchar *text,
 
        undo_check_size(undostruct);
 
-       debug_print("New: %s Action: %d Paste: %d\n", text, action, undostruct->paste);
-
+       vadj = GTK_ADJUSTMENT(gtk_text_view_get_vadjustment(
+                               GTK_TEXT_VIEW(undostruct->textview)));
        undoinfo = undo_object_new(g_strdup(text), start_pos, end_pos, action,
-                                  GTK_ADJUSTMENT(GTK_STEXT(undostruct->text)->vadj)->value);
+                                  gtk_adjustment_get_value(vadj));
 
        if (end_pos - start_pos != 1 || text[0] == '\n')
                undoinfo->mergeable = FALSE;
@@ -357,65 +348,82 @@ static void undo_add(const gchar *text,
 void undo_undo(UndoMain *undostruct) 
 {
        UndoInfo *undoinfo;
-       guint start_pos, end_pos;
+       GtkTextView *textview;
+       GtkTextBuffer *buffer;
+       GtkTextIter iter, start_iter, end_iter;
+       GtkTextMark *mark;
 
-       g_return_if_fail(undostruct != NULL);
+       cm_return_if_fail(undostruct != NULL);
 
        if (undostruct->undo == NULL) return;
 
        /* The undo data we need is always at the top op the
           stack. So, therefore, the first one */
        undoinfo = (UndoInfo *)undostruct->undo->data;
-       g_return_if_fail(undoinfo != NULL);
+       cm_return_if_fail(undoinfo != NULL);
        undoinfo->mergeable = FALSE;
        undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
        undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
 
+       textview = undostruct->textview;
+       buffer = gtk_text_view_get_buffer(textview);
+
+       undo_block(undostruct);
+
        /* Check if there is a selection active */
-       start_pos = GTK_EDITABLE(undostruct->text)->selection_start_pos;
-       end_pos   = GTK_EDITABLE(undostruct->text)->selection_end_pos;
-       if ((start_pos > 0 || end_pos > 0) && (start_pos != end_pos))
-               gtk_editable_select_region(GTK_EDITABLE(undostruct->text),
-                                          0, 0);
+       mark = gtk_text_buffer_get_insert(buffer);
+       gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
+       gtk_text_buffer_place_cursor(buffer, &iter);
 
        /* Move the view (scrollbars) to the correct position */
        gtk_adjustment_set_value
-               (GTK_ADJUSTMENT(GTK_STEXT(undostruct->text)->vadj),
+               (GTK_ADJUSTMENT(gtk_text_view_get_vadjustment(textview)),
                 undoinfo->window_position);
-
+       
        switch (undoinfo->action) {
        case UNDO_ACTION_DELETE:
-               gtk_stext_set_point(GTK_STEXT(undostruct->text), undoinfo->start_pos);
-               gtk_stext_insert(GTK_STEXT(undostruct->text), NULL, NULL, NULL, undoinfo->text, -1);
-               debug_print("UNDO_ACTION_DELETE %s\n", undoinfo->text);
+               gtk_text_buffer_get_iter_at_offset(buffer, &iter, undoinfo->start_pos);
+               gtk_text_buffer_insert(buffer, &iter, undoinfo->text, -1);
                break;
        case UNDO_ACTION_INSERT:
-               gtk_stext_set_point(GTK_STEXT(undostruct->text), undoinfo->end_pos);
-               gtk_stext_backward_delete(GTK_STEXT(undostruct->text), undoinfo->end_pos-undoinfo->start_pos);
-               debug_print("UNDO_ACTION_INSERT %d\n", undoinfo->end_pos-undoinfo->start_pos);
+               gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, undoinfo->start_pos);
+               gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, undoinfo->end_pos);
+               gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
                break;
        case UNDO_ACTION_REPLACE_INSERT:
-               gtk_stext_set_point(GTK_STEXT(undostruct->text), undoinfo->end_pos);
-               gtk_stext_backward_delete(GTK_STEXT(undostruct->text), undoinfo->end_pos-undoinfo->start_pos);
-               debug_print("UNDO_ACTION_REPLACE %s\n", undoinfo->text);
-               /* "pull" another data structure from the list */
-               undoinfo = (UndoInfo *)undostruct->undo->data;
-               g_return_if_fail(undoinfo != NULL);
-               undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
-               undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
-               g_return_if_fail(undoinfo->action == UNDO_ACTION_REPLACE_DELETE);
-               gtk_stext_set_point(GTK_STEXT(undostruct->text), undoinfo->start_pos);
-               gtk_stext_insert(GTK_STEXT(undostruct->text), NULL, NULL, NULL, undoinfo->text, -1);
-               debug_print("UNDO_ACTION_REPLACE %s\n", undoinfo->text);
+               gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, undoinfo->start_pos);
+               gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, undoinfo->end_pos);
+               gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
+               /* "pull" previous matching DELETE data structure from the list */
+               if (undostruct->undo){
+                       undoinfo = (UndoInfo *)undostruct->undo->data;
+                       undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
+                       undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
+                       cm_return_if_fail(undoinfo != NULL);
+                       cm_return_if_fail(undoinfo->action == UNDO_ACTION_REPLACE_DELETE);
+                       gtk_text_buffer_insert(buffer, &start_iter, undoinfo->text, -1);
+               }
                break;
        case UNDO_ACTION_REPLACE_DELETE:
-               g_warning("This should not happen. UNDO_REPLACE_DELETE");
+               gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, undoinfo->start_pos);
+               gtk_text_buffer_insert(buffer, &start_iter, undoinfo->text, -1);
+               /* "pull" previous matching INSERT data structure from the list */
+               if (undostruct->undo){
+                       undoinfo = (UndoInfo *)undostruct->undo->data;
+                       undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
+                       undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
+                       cm_return_if_fail(undoinfo != NULL);
+                       cm_return_if_fail(undoinfo->action == UNDO_ACTION_REPLACE_INSERT);
+                       gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, undoinfo->start_pos);
+                       gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, undoinfo->end_pos);
+                       gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
+               }
                break;
        default:
                g_assert_not_reached();
                break;
        }
-
+       
        undostruct->change_state_func(undostruct,
                                      UNDO_STATE_UNCHANGED, UNDO_STATE_TRUE,
                                      undostruct->change_state_data);
@@ -425,6 +433,8 @@ void undo_undo(UndoMain *undostruct)
                                              UNDO_STATE_FALSE,
                                              UNDO_STATE_UNCHANGED,
                                              undostruct->change_state_data);
+
+       undo_unblock(undostruct);
 }
 
 /**
@@ -437,63 +447,71 @@ void undo_undo(UndoMain *undostruct)
 void undo_redo(UndoMain *undostruct) 
 {
        UndoInfo *redoinfo;
-       guint start_pos, end_pos;
+       GtkTextView *textview;
+       GtkTextBuffer *buffer;
+       GtkTextIter iter, start_iter, end_iter;
+       GtkTextMark *mark;
 
-       g_return_if_fail(undostruct != NULL);
+       cm_return_if_fail(undostruct != NULL);
 
        if (undostruct->redo == NULL) return;
 
        redoinfo = (UndoInfo *)undostruct->redo->data;
-       g_return_if_fail (redoinfo != NULL);
+       cm_return_if_fail (redoinfo != NULL);
        undostruct->undo = g_list_prepend(undostruct->undo, redoinfo);
        undostruct->redo = g_list_remove(undostruct->redo, redoinfo);
 
+       textview = undostruct->textview;
+       buffer = gtk_text_view_get_buffer(textview);
+
+       undo_block(undostruct);
+
        /* Check if there is a selection active */
-       start_pos = GTK_EDITABLE(undostruct->text)->selection_start_pos;
-       end_pos   = GTK_EDITABLE(undostruct->text)->selection_end_pos;
-       if ((start_pos > 0 || end_pos > 0) && (start_pos != end_pos))
-               gtk_editable_select_region(GTK_EDITABLE(undostruct->text), 0, 0);
+       mark = gtk_text_buffer_get_insert(buffer);
+       gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
+       gtk_text_buffer_place_cursor(buffer, &iter);
 
        /* Move the view to the right position. */
-       gtk_adjustment_set_value(GTK_ADJUSTMENT(GTK_STEXT(undostruct->text)->vadj), 
+       gtk_adjustment_set_value(gtk_text_view_get_vadjustment(textview), 
                                 redoinfo->window_position);
 
        switch (redoinfo->action) {
        case UNDO_ACTION_INSERT:
-               gtk_stext_set_point(GTK_STEXT(undostruct->text),
-                                  redoinfo->start_pos);
-               gtk_stext_insert(GTK_STEXT(undostruct->text), NULL, NULL, 
-                               NULL, redoinfo->text, -1);
-               debug_print("UNDO_ACTION_DELETE %s\n",redoinfo->text);
+               gtk_text_buffer_get_iter_at_offset(buffer, &iter, redoinfo->start_pos);
+               gtk_text_buffer_insert(buffer, &iter, redoinfo->text, -1);
                break;
        case UNDO_ACTION_DELETE:
-               gtk_stext_set_point(GTK_STEXT(undostruct->text),
-                                  redoinfo->end_pos);
-               gtk_stext_backward_delete
-                       (GTK_STEXT(undostruct->text), 
-                        redoinfo->end_pos - redoinfo->start_pos);
-               debug_print("UNDO_ACTION_INSERT %d\n", 
-                           redoinfo->end_pos-redoinfo->start_pos);
+               gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, redoinfo->start_pos);
+               gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, redoinfo->end_pos);
+               gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
                break;
        case UNDO_ACTION_REPLACE_DELETE:
-               gtk_stext_set_point(GTK_STEXT(undostruct->text),
-                                  redoinfo->end_pos);
-               gtk_stext_backward_delete
-                       (GTK_STEXT(undostruct->text), 
-                        redoinfo->end_pos - redoinfo->start_pos);
-               /* "pull" another data structure from the list */
+               gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, redoinfo->start_pos);
+               gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, redoinfo->end_pos);
+               gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
+               debug_print("UNDO_ACTION_REPLACE %s\n", redoinfo->text);
+               /* "pull" previous matching INSERT data structure from the list */
                redoinfo = (UndoInfo *)undostruct->redo->data;
-               g_return_if_fail(redoinfo != NULL);
+               cm_return_if_fail(redoinfo != NULL);
                undostruct->undo = g_list_prepend(undostruct->undo, redoinfo);
                undostruct->redo = g_list_remove(undostruct->redo, redoinfo);
-               g_return_if_fail(redoinfo->action == UNDO_ACTION_REPLACE_INSERT);
-               gtk_stext_set_point(GTK_STEXT(undostruct->text),
-                                  redoinfo->start_pos);
-               gtk_stext_insert(GTK_STEXT(undostruct->text), NULL, NULL, 
-                               NULL, redoinfo->text, -1);
+               cm_return_if_fail(redoinfo->action == UNDO_ACTION_REPLACE_INSERT);
+               gtk_text_buffer_insert(buffer, &start_iter, redoinfo->text, -1);
                break;
        case UNDO_ACTION_REPLACE_INSERT:
-               g_warning("This should not happen. Redo: UNDO_REPLACE_INSERT");
+               gtk_text_buffer_get_iter_at_offset(buffer, &iter, redoinfo->start_pos);
+               gtk_text_buffer_insert(buffer, &iter, redoinfo->text, -1);
+               /* "pull" previous matching DELETE structure from the list */
+               redoinfo = (UndoInfo *)undostruct->redo->data;
+               /* Do nothing if we redo from a middle-click button
+                * and next action is not UNDO_ACTION_REPLACE_DELETE */
+               if (redoinfo && redoinfo->action == UNDO_ACTION_REPLACE_DELETE) {
+                       undostruct->undo = g_list_prepend(undostruct->undo, redoinfo);
+                       undostruct->redo = g_list_remove(undostruct->redo, redoinfo);
+                       gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, redoinfo->start_pos);
+                       gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, redoinfo->end_pos);
+                       gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
+               }
                break;
        default:
                g_assert_not_reached();
@@ -509,56 +527,245 @@ void undo_redo(UndoMain *undostruct)
                                              UNDO_STATE_UNCHANGED,
                                              UNDO_STATE_FALSE,
                                              undostruct->change_state_data);
+
+       undo_unblock(undostruct);
+}
+
+void undo_block(UndoMain *undostruct)
+{
+       GtkTextBuffer *buffer;
+
+       cm_return_if_fail(GTK_IS_TEXT_VIEW(undostruct->textview));
+
+       buffer = gtk_text_view_get_buffer(undostruct->textview);
+       g_signal_handlers_block_by_func(buffer, undo_insert_text_cb, undostruct);
+       g_signal_handlers_block_by_func(buffer, undo_delete_text_cb, undostruct);
+       g_signal_handlers_block_by_func(buffer, undo_paste_clipboard_cb,
+                                         undostruct);
 }
 
-void undo_insert_text_cb(GtkEditable *editable, gchar *new_text,
-                        gint new_text_length, gint *position, 
+void undo_unblock(UndoMain *undostruct)
+{
+       GtkTextBuffer *buffer;
+
+       cm_return_if_fail(GTK_IS_TEXT_VIEW(undostruct->textview));
+
+       buffer = gtk_text_view_get_buffer(undostruct->textview);
+       g_signal_handlers_unblock_by_func(buffer, undo_insert_text_cb, undostruct);
+       g_signal_handlers_unblock_by_func(buffer, undo_delete_text_cb, undostruct);
+       g_signal_handlers_unblock_by_func(buffer, undo_paste_clipboard_cb,
+                                         undostruct);
+}
+
+/* Init the WrapInfo structure */
+static void init_wrap_undo(UndoMain *undostruct)
+{
+       GtkTextBuffer *buffer;
+       GtkTextIter start, end;
+
+       cm_return_if_fail(undostruct != NULL);
+       cm_return_if_fail(undostruct->wrap_info == NULL);
+
+       undostruct->wrap_info = g_new0(UndoWrap, 1);
+
+       /* Save the whole buffer as original contents. We'll retain the
+        * changed region when exiting wrap mode.
+        */
+       buffer = gtk_text_view_get_buffer(undostruct->textview);
+       gtk_text_buffer_get_start_iter(buffer, &start);
+       gtk_text_buffer_get_end_iter(buffer, &end);
+       undostruct->wrap_info->pre_wrap_content
+               = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
+
+       undostruct->wrap_info->lock = 0;
+
+       /* start_pos == -1 means nothing changed yet. */
+       undostruct->wrap_info->start_pos = -1;
+       undostruct->wrap_info->end_pos = -1;
+       undostruct->wrap_info->len_change = 0;
+}
+
+static void end_wrap_undo(UndoMain *undostruct)
+{
+       GtkTextBuffer *buffer;
+       GtkTextIter start, end;
+       gchar *old_contents = NULL;
+       gchar *cur_contents = NULL;
+       gchar *new_contents = NULL;
+
+       cm_return_if_fail(undostruct != NULL);
+       cm_return_if_fail(undostruct->wrap_info != NULL);
+
+       /* If start_pos is still == -1, it means nothing changed. */
+       if (undostruct->wrap_info->start_pos == -1)
+               goto cleanup;
+
+       cm_return_if_fail(undostruct->wrap_info->end_pos > undostruct->wrap_info->start_pos);
+       cm_return_if_fail(undostruct->wrap_info->end_pos - undostruct->wrap_info->len_change > undostruct->wrap_info->start_pos);
+
+       /* get the whole new (wrapped) contents */
+       buffer = gtk_text_view_get_buffer(undostruct->textview);
+       gtk_text_buffer_get_start_iter(buffer, &start);
+       gtk_text_buffer_get_end_iter(buffer, &end);
+       cur_contents = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
+
+       debug_print("wrapping done from %d to %d, len change: %d\n",
+               undostruct->wrap_info->start_pos,
+               undostruct->wrap_info->end_pos,
+               undostruct->wrap_info->len_change);
+
+       /* keep the relevant old unwrapped part, which is what
+        * was between start_pos & end_pos - len_change
+        */
+       old_contents = g_utf8_substring(
+                       undostruct->wrap_info->pre_wrap_content,
+                       undostruct->wrap_info->start_pos,
+                       undostruct->wrap_info->end_pos
+                       - undostruct->wrap_info->len_change);
+
+       /* and get the changed contents, from start_pos to end_pos. */
+       new_contents = g_utf8_substring(
+                       cur_contents,
+                       undostruct->wrap_info->start_pos,
+                       undostruct->wrap_info->end_pos);
+
+       /* add the deleted (unwrapped) text to the undo pile */
+       undo_add(old_contents,
+                undostruct->wrap_info->start_pos,
+                undostruct->wrap_info->end_pos
+                 - undostruct->wrap_info->len_change,
+                UNDO_ACTION_REPLACE_DELETE,
+                undostruct);
+
+       /* add the inserted (wrapped) text to the undo pile */
+       undo_add(new_contents,
+                undostruct->wrap_info->start_pos,
+                undostruct->wrap_info->end_pos,
+                UNDO_ACTION_REPLACE_INSERT,
+                undostruct);
+
+       g_free(old_contents);
+       g_free(cur_contents);
+       g_free(new_contents);
+cleanup:
+       g_free(undostruct->wrap_info->pre_wrap_content);
+       g_free(undostruct->wrap_info);
+       undostruct->wrap_info = NULL;
+}
+
+static void update_wrap_undo(UndoMain *undostruct, const gchar *text, int start, 
+                            int end, UndoAction action)
+{
+       gint len = end - start;
+
+       /* If we don't yet have a start position, or farther than
+        * current, store it.
+        */
+       if (undostruct->wrap_info->start_pos == -1
+        || start < undostruct->wrap_info->start_pos) {
+               undostruct->wrap_info->start_pos = start;
+       }
+
+       if (action == UNDO_ACTION_INSERT) {
+               /* If inserting, the end of the region is at the end of the
+                * change, and the total length of the changed region
+                * increases.
+                */
+               if (end > undostruct->wrap_info->end_pos) {
+                       undostruct->wrap_info->end_pos = end;
+               }
+               undostruct->wrap_info->len_change += len;
+       } else if (action == UNDO_ACTION_DELETE) {
+               /* If deleting, the end of the region is at the start of the
+                * change, and the total length of the changed region
+                * decreases.
+                */
+               if (start > undostruct->wrap_info->end_pos) {
+                       undostruct->wrap_info->end_pos = start;
+               }
+               undostruct->wrap_info->len_change -= len;
+       }
+}
+
+/* Set wrapping mode, in which changes are agglomerated until
+ * the end of wrapping mode.
+ */
+void undo_wrapping(UndoMain *undostruct, gboolean wrap)
+{
+       if (wrap) {
+               /* Start (or go deeper in) wrapping mode */
+               if (undostruct->wrap_info == NULL)
+                       init_wrap_undo(undostruct);
+               undostruct->wrap_info->lock++;
+       } else if (undostruct->wrap_info != NULL) {
+               /* exit (& possible stop) one level of wrapping mode */
+               undostruct->wrap_info->lock--;
+               if (undostruct->wrap_info->lock == 0)
+                       end_wrap_undo(undostruct);
+       } else {
+               g_warning("undo already out of wrap mode");
+       }
+}
+
+void undo_insert_text_cb(GtkTextBuffer *textbuf, GtkTextIter *iter,
+                        gchar *new_text, gint new_text_length,
                         UndoMain *undostruct) 
 {
        gchar *text_to_insert;
-       size_t wlen;
+       gint pos;
+       glong utf8_len;
 
        if (prefs_common.undolevels <= 0) return;
 
+       pos = gtk_text_iter_get_offset(iter);
        Xstrndup_a(text_to_insert, new_text, new_text_length, return);
-       if (MB_CUR_MAX > 1) {
-               wchar_t *wstr;
+       utf8_len = g_utf8_strlen(text_to_insert, -1);
 
-               wstr = g_new(wchar_t, new_text_length + 1);
-               wlen = mbstowcs(wstr, text_to_insert, new_text_length + 1);
-               g_free(wstr);
-               if (wlen < 0) return;
-       } else
-               wlen = new_text_length;
+       if (undostruct->wrap_info != NULL) {
+               update_wrap_undo(undostruct, text_to_insert, 
+                                pos, pos + utf8_len, UNDO_ACTION_INSERT);
+               return;
+       }
 
-       undo_add(text_to_insert, *position, *position + wlen,
+       debug_print("add:undo add %d-%ld\n", pos, utf8_len);
+       undo_add(text_to_insert, pos, pos + utf8_len,
                 UNDO_ACTION_INSERT, undostruct);
 }
 
-void undo_delete_text_cb(GtkEditable *editable, gint start_pos,
-                        gint end_pos, UndoMain *undostruct) 
+void undo_delete_text_cb(GtkTextBuffer *textbuf, GtkTextIter *start,
+                        GtkTextIter *end, UndoMain *undostruct) 
 {
        gchar *text_to_delete;
+       gint start_pos, end_pos;
 
        if (prefs_common.undolevels <= 0) return;
-       if (start_pos == end_pos) return;
 
-       text_to_delete = gtk_editable_get_chars(GTK_EDITABLE(editable),
-                                               start_pos, end_pos);
+       text_to_delete = gtk_text_buffer_get_text(textbuf, start, end, FALSE);
+       if (!text_to_delete || !*text_to_delete) return;
+
+       start_pos = gtk_text_iter_get_offset(start);
+       end_pos   = gtk_text_iter_get_offset(end);
+
+       if (undostruct->wrap_info != NULL) {
+               update_wrap_undo(undostruct, text_to_delete, start_pos, end_pos, UNDO_ACTION_DELETE);
+               return;
+       } 
+       debug_print("del:undo add %d-%d\n", start_pos, end_pos);
        undo_add(text_to_delete, start_pos, end_pos, UNDO_ACTION_DELETE,
                 undostruct);
        g_free(text_to_delete);
 }
 
-void undo_paste_clipboard_cb(GtkEditable *editable, UndoMain *undostruct)
+void undo_paste_clipboard(GtkTextView *textview, UndoMain *undostruct)
 {
-       if (editable->clipboard_text == NULL) return;
+       undo_paste_clipboard_cb(textview, undostruct);
+}
 
-       debug_print("before Paste: %d\n", undostruct->paste);
+static void undo_paste_clipboard_cb(GtkTextView *textview, UndoMain *undostruct)
+{
        if (prefs_common.undolevels > 0)
-               if (undo_get_selection(editable, NULL, NULL))
+               if (undo_get_selection(textview, NULL, NULL))
                        undostruct->paste = TRUE;
-       debug_print("after Paste: %d\n", undostruct->paste);
 }
 
 /**
@@ -571,12 +778,17 @@ void undo_paste_clipboard_cb(GtkEditable *editable, UndoMain *undostruct)
  *
  * Return Value: TRUE if there is a selection active, FALSE if not
  **/
-static gint undo_get_selection(GtkEditable *text, guint *start, guint *end) 
+static gint undo_get_selection(GtkTextView *textview, guint *start, guint *end) 
 {
+       GtkTextBuffer *buffer;
+       GtkTextIter start_iter, end_iter;
        guint start_pos, end_pos;
 
-       start_pos = text->selection_start_pos;
-       end_pos   = text->selection_end_pos;
+       buffer = gtk_text_view_get_buffer(textview);
+       gtk_text_buffer_get_selection_bounds(buffer, &start_iter, &end_iter);
+
+       start_pos = gtk_text_iter_get_offset(&start_iter);
+       end_pos   = gtk_text_iter_get_offset(&end_iter);
 
        /* The user can select from end to start too. If so, swap it*/
        if (end_pos < start_pos) {