URI: 
       tMove undo handling to buffer - ledit - Text editor (WIP)
  HTML git clone git://lumidify.org/ledit.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/ledit.git (encrypted, but very slow)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 3572b3d7dd300642a94067f72f30fc83c8651e39
   DIR parent 123a3087ad0bc4f772f0cd0a17caf95304092f87
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Thu,  9 Dec 2021 11:01:05 +0100
       
       Move undo handling to buffer
       
       Diffstat:
         M buffer.c                            |     336 +++++++++++++++++++++++++------
         M buffer.h                            |     197 +++++++++++--------------------
         M keys_basic.c                        |     109 ++++++++++++-------------------
         M keys_command.c                      |      47 +++++++++++++------------------
         M undo.c                              |       6 ++++++
         M undo.h                              |       9 +++++++++
         M view.c                              |     361 +++++++++++--------------------
         M view.h                              |      23 ++++++++++++++++-------
       
       8 files changed, 553 insertions(+), 535 deletions(-)
       ---
   DIR diff --git a/buffer.c b/buffer.c
       t@@ -73,10 +73,103 @@ static char *strchr_len(char *text, char c, size_t len);
         */
        static void init_line(ledit_buffer *buffer, ledit_line *line);
        
       +/*
       + * Insert 'src_len' bytes from 'src_line' starting at byte position 'src_index'
       + * into line 'dst_line' at byte position 'dst_index'.
       + * 'dst_line' must not be the same as 'src_line'.
       + * If 'text_ret' is not NULL, the copied text is additionally copied into 'text_ret'.
       + * This function does not update the views or normalize the lines, so it should
       + * only be used for efficiency purposes when performing multiple operations.
       + */
       +static void buffer_insert_text_from_line_base(
       +    ledit_buffer *buffer,
       +    size_t dst_line, size_t dst_index,
       +    size_t src_line, size_t src_index, size_t src_len,
       +    txtbuf *text_ret
       +);
       +
       +/*
       + * Insert text 'text' with length 'len' at line 'line_index' and byte position 'index'.
       + * The text must not contain newlines.
       + * This function does not update the views or normalize the lines, so it should
       + * only be used for efficiency purposes when performing multiple operations.
       + */
       +static void buffer_insert_text_base(
       +    ledit_buffer *buffer,
       +    size_t line_index, size_t index,
       +    char *text, size_t len
       +);
       +
       +/* Insert text 'text' with length 'len' at line 'line_index' and byte position 'index.
       + * The text may contain newlines.
       + * If end_line_ret is not NULL, the line index at the end of the insertion is
       + * written into it.
       + * If end_byte_ret is not NULL, the byte position at the end of the insertion is
       + * written into it.
       + * This function does not update the views or normalize the lines, so it should
       + * only be used for efficiency purposes when performing multiple operations.
       + */
       +static void buffer_insert_text_with_newlines_base(
       +    ledit_buffer *buffer,
       +    size_t line_index, size_t index,
       +    char *text, size_t len,
       +    size_t *end_line_ret, size_t *end_char_ret
       +);
       +
       +/*
       + * Same as buffer_insert_text_with_newlines_base, but the views are updated afterwards.
       + */
       +static void buffer_insert_text_with_newlines(
       +    ledit_buffer *buffer,
       +    size_t line_index, size_t index,
       +    char *text, size_t len,
       +    size_t *end_line_ret, size_t *end_char_ret
       +);
       +
       +/*
       + * Append line after line at 'line_index'.
       + * if 'break_text' is not 0, the text on line 'line_index' starting at
       + * byte index 'text_index' is moved to the newly appended line.
       + * The views are notified that a line has been appended, but not told to update
       + * their line heights and offsets.
       + */
       +static void buffer_append_line_base(ledit_buffer *buffer, size_t line_index, size_t text_index, int break_text);
       +
       +/*
       + * Delete lines between 'index1' and 'index2' (inclusive).
       + * The views are notified of the deletion but not told to
       + * update their line heights and offsets.
       + */
       +static void buffer_delete_line_entries_base(ledit_buffer *buffer, size_t index1, size_t index2);
       +
       +/*
       + * Delete the section of line 'line' starting at byte 'start' with length 'length'
       + * and notify the views.
       + * Note that this does not tell the views to recalculate their line heights and offsets.
       + */
       +static void buffer_delete_line_section_base(ledit_buffer *buffer, size_t line, size_t start, size_t length);
       +
        static void marklist_destroy(ledit_buffer_marklist *marklist);
        static ledit_buffer_marklist *marklist_create(void);
        
        static void
       +swap_sz(size_t *a, size_t *b) {
       +        size_t tmp = *a;
       +        *a = *b;
       +        *b = tmp;
       +}
       +
       +static void
       +sort_range(size_t *l1, size_t *b1, size_t *l2, size_t *b2) {
       +        if (*l1 == *l2 && *b1 > *b2) {
       +                swap_sz(b1, b2);
       +        } else if (*l1 > *l2) {
       +                swap_sz(l1, l2);
       +                swap_sz(b1, b2);
       +        }
       +}
       +
       +static void
        marklist_destroy(ledit_buffer_marklist *marklist) {
                for (size_t i = 0; i < marklist->len; i++) {
                        free(marklist->marks[i].text);
       t@@ -324,22 +417,10 @@ buffer_normalize_line(ledit_line *line) {
        
        /* FIXME: To simplify this a bit, maybe just copy text to txtbuf first and
           then insert it in one go instead of having this complex logic */
       -void
       -buffer_insert_text_from_line(
       -    ledit_buffer *buffer,
       -    size_t dst_line, size_t dst_index,
       -    size_t src_line, size_t src_index, size_t src_len,
       -    txtbuf *text_ret) {
       -        buffer_insert_text_from_line_base(
       -            buffer, dst_line, dst_index, src_line, src_index, src_len, text_ret
       -        );
       -        buffer_recalc_line(buffer, dst_line);
       -}
       -
        /* FIXME: check if there can be bugs when a newline is inserted in some way
           other than pasting or pressing enter */
        
       -void
       +static void
        buffer_insert_text_from_line_base(
            ledit_buffer *buffer,
            size_t dst_line, size_t dst_index,
       t@@ -452,13 +533,7 @@ resize_and_move_line_gap(ledit_buffer *buffer, size_t min_size, size_t index) {
                );
        }
        
       -void
       -buffer_insert_text(ledit_buffer *buffer, size_t line_index, size_t index, char *text, size_t len) {
       -        buffer_insert_text_base(buffer, line_index, index, text, len);
       -        buffer_recalc_line(buffer, line_index);
       -}
       -
       -void
       +static void
        buffer_insert_text_base(ledit_buffer *buffer, size_t line_index, size_t index, char *text, size_t len) {
                ledit_line *line = buffer_get_line(buffer, line_index);
                /* \0 is not included in line->len */
       t@@ -487,7 +562,7 @@ strchr_len(char *text, char c, size_t len) {
        }
        
        /* FIXME: make these functions that call recalc* also be final as described above */
       -void
       +static void
        buffer_insert_text_with_newlines(
            ledit_buffer *buffer,
            size_t line_index, size_t index,
       t@@ -507,7 +582,7 @@ buffer_insert_text_with_newlines(
        }
        
        /* FIXME: also look for \r */
       -void
       +static void
        buffer_insert_text_with_newlines_base(
            ledit_buffer *buffer,
            size_t line_index, size_t index,
       t@@ -546,14 +621,8 @@ init_line(ledit_buffer *buffer, ledit_line *line) {
                line->len = 0;
        }
        
       -void
       -buffer_append_line(ledit_buffer *buffer, size_t line_index, size_t text_index, int break_text) {
       -        buffer_append_line_base(buffer, line_index, text_index, break_text);
       -        buffer_recalc_from_line(buffer, line_index);
       -}
       -
        /* FIXME: error checking (index out of bounds, etc.) */
       -void
       +static void
        buffer_append_line_base(ledit_buffer *buffer, size_t line_index, size_t text_index, int break_text) {
                size_t new_len = buffer->lines_num + 1;
                if (new_len <= buffer->lines_num)
       t@@ -582,15 +651,8 @@ buffer_append_line_base(ledit_buffer *buffer, size_t line_index, size_t text_ind
                }
        }
        
       -/* FIXME: set offset to 0 when recalculating first line? */
       -void
       -buffer_delete_line_entries(ledit_buffer *buffer, size_t index1, size_t index2) {
       -        buffer_delete_line_entries_base(buffer, index1, index2);
       -        buffer_recalc_from_line(buffer, index1 > 0 ? index1 - 1 : 0);
       -}
       -
        /* IMPORTANT: buffer_recalc_from_line needs to be called sometime after this! */
       -void
       +static void
        buffer_delete_line_entries_base(ledit_buffer *buffer, size_t index1, size_t index2) {
                ledit_line *l;
                assert (index2 >= index1);
       t@@ -607,16 +669,6 @@ buffer_delete_line_entries_base(ledit_buffer *buffer, size_t index1, size_t inde
                }
        }
        
       -void
       -buffer_delete_line_entry(ledit_buffer *buffer, size_t index) {
       -        buffer_delete_line_entries(buffer, index, index);
       -}
       -
       -void
       -buffer_delete_line_entry_base(ledit_buffer *buffer, size_t index) {
       -        buffer_delete_line_entries_base(buffer, index, index);
       -}
       -
        ledit_line *
        buffer_get_line(ledit_buffer *buffer, size_t index) {
                assert(index < buffer->lines_num);
       t@@ -753,7 +805,7 @@ line_byte_to_char(ledit_line *line, size_t byte) {
                return c;
        }
        
       -void
       +static void
        buffer_delete_line_section_base(ledit_buffer *buffer, size_t line, size_t start, size_t length) {
                ledit_line *l = buffer_get_line(buffer, line);
                if (start <= l->gap && start + length >= l->gap) {
       t@@ -778,24 +830,178 @@ buffer_delete_line_section_base(ledit_buffer *buffer, size_t line, size_t start,
                }
        }
        
       -size_t
       -buffer_delete_unicode_char(ledit_buffer *buffer, size_t line_index, size_t byte_index, int dir) {
       -        size_t new_index = buffer_delete_unicode_char_base(buffer, line_index, byte_index, dir);
       -        buffer_recalc_line(buffer, line_index);
       -        return new_index;
       +static void
       +delete_range_base(
       +    ledit_buffer *buffer,
       +    size_t line_index1, size_t byte_index1,
       +    size_t line_index2, size_t byte_index2,
       +    txtbuf *text_ret) {
       +        sort_range(&line_index1, &byte_index1, &line_index2, &byte_index2);
       +        if (line_index1 == line_index2) {
       +                if (text_ret) {
       +                        buffer_copy_text_to_txtbuf(
       +                            buffer, text_ret,
       +                            line_index1, byte_index1,
       +                            line_index2, byte_index2
       +                        );
       +                }
       +                buffer_delete_line_section_base(
       +                    buffer, line_index1, byte_index1, byte_index2 - byte_index1
       +                );
       +        } else {
       +                if (text_ret) {
       +                        buffer_copy_text_to_txtbuf(
       +                            buffer, text_ret,
       +                            line_index1, byte_index1,
       +                            line_index2, byte_index2
       +                        );
       +                }
       +                ledit_line *line1 = buffer_get_line(buffer, line_index1);
       +                ledit_line *line2 = buffer_get_line(buffer, line_index2);
       +                buffer_delete_line_section_base(
       +                    buffer, line_index1, byte_index1, line1->len - byte_index1
       +                );
       +                buffer_insert_text_from_line_base(
       +                    buffer,
       +                    line_index1, byte_index1,
       +                    line_index2, byte_index2,
       +                    line2->len - byte_index2, NULL
       +                );
       +                buffer_delete_line_entries_base(
       +                    buffer, line_index1 + 1, line_index2
       +                );
       +        }
        }
        
       -size_t
       -buffer_delete_unicode_char_base(ledit_buffer *buffer, size_t line_index, size_t byte_index, int dir) {
       -        ledit_line *l = buffer_get_line(buffer, line_index);
       -        size_t new_index = byte_index;
       -        if (dir < 0) {
       -                size_t i = line_prev_utf8(l, byte_index);
       -                buffer_delete_line_section_base(buffer, line_index, i, byte_index - i);
       -                new_index = i;
       -        } else {
       -                size_t i = line_next_utf8(l, byte_index);
       -                buffer_delete_line_section_base(buffer, line_index, byte_index, i - byte_index);
       +static void
       +undo_insert_helper(void *data, size_t line, size_t byte, char *text, size_t text_len) {
       +        ledit_buffer *buffer = (ledit_buffer *)data;
       +        buffer_insert_text_with_newlines_base(buffer, line, byte, text, text_len, NULL, NULL);
       +}
       +
       +static void
       +undo_delete_helper(void *data, size_t line1, size_t byte1, size_t line2, size_t byte2) {
       +        ledit_buffer *buffer = (ledit_buffer *)data;
       +        delete_range_base(buffer, line1, byte1, line2, byte2, NULL);
       +}
       +
       +void
       +buffer_undo(ledit_buffer *buffer, enum ledit_mode mode, size_t *cur_line, size_t *cur_byte) {
       +        size_t min_line;
       +        ledit_undo(
       +            buffer->undo, mode, buffer, &undo_insert_helper,
       +            &undo_delete_helper, cur_line, cur_byte, &min_line
       +        );
       +        /* FIXME: why is this check here? */
       +        if (min_line < buffer->lines_num) {
       +                buffer_recalc_all_views_from_line(
       +                    buffer, min_line > 0 ? min_line - 1 : min_line
       +                );
                }
       -        return new_index;
       +}
       +
       +void
       +buffer_redo(ledit_buffer *buffer, enum ledit_mode mode, size_t *cur_line, size_t *cur_byte) {
       +        size_t min_line;
       +        ledit_redo(
       +            buffer->undo, mode, buffer, &undo_insert_helper,
       +            &undo_delete_helper, cur_line, cur_byte, &min_line
       +        );
       +        if (min_line < buffer->lines_num) {
       +                buffer_recalc_all_views_from_line(
       +                    buffer, min_line > 0 ? min_line - 1 : min_line
       +                );
       +        }
       +}
       +
       +void
       +buffer_delete_with_undo_base(
       +    ledit_buffer *buffer, ledit_range cur_range,
       +    int start_undo_group, enum ledit_mode mode, /* for undo */
       +    size_t line_index1, size_t byte_index1,
       +    size_t line_index2, size_t byte_index2,
       +    txtbuf *text_ret) {
       +        /* FIXME: global txtbuf to avoid allocating each time */
       +        txtbuf *buf = text_ret != NULL ? text_ret : txtbuf_new();
       +        sort_range(&line_index1, &byte_index1, &line_index2, &byte_index2);
       +        delete_range_base(
       +            buffer, line_index1, byte_index1, line_index2, byte_index2, buf
       +        );
       +        ledit_range del_range = {
       +            .line1 = line_index1, .byte1 = byte_index1,
       +            .line2 = line_index2, .byte2 = byte_index2
       +        };
       +        undo_push_delete(
       +            buffer->undo, buf, del_range, cur_range, start_undo_group, mode
       +        );
       +        if (text_ret == NULL)
       +                txtbuf_destroy(buf);
       +}
       +
       +void
       +buffer_delete_with_undo(
       +    ledit_buffer *buffer, ledit_range cur_range,
       +    int start_undo_group, enum ledit_mode mode, /* for undo */
       +    size_t line_index1, size_t byte_index1,
       +    size_t line_index2, size_t byte_index2,
       +    txtbuf *text_ret) {
       +        buffer_delete_with_undo_base(
       +            buffer, cur_range,
       +            start_undo_group, mode,
       +            line_index1, byte_index1,
       +            line_index2, byte_index2,
       +            text_ret
       +        );
       +        size_t min = line_index1 < line_index2 ? line_index1 : line_index2;
       +        buffer_recalc_all_views_from_line(buffer, min);
       +}
       +
       +void
       +buffer_insert_with_undo_base(
       +    ledit_buffer *buffer,
       +    ledit_range cur_range, int set_range_end,
       +    int start_undo_group, enum ledit_mode mode,
       +    size_t line, size_t byte,
       +    char *text, size_t len,
       +    size_t *line_ret, size_t *byte_ret) {
       +        txtbuf ins_buf = {.text = text, .len = len, .cap = len};
       +        ledit_range ins_range;
       +        ins_range.line1 = line;
       +        ins_range.byte1 = byte;
       +        size_t new_line, new_byte;
       +        buffer_insert_text_with_newlines_base(
       +            buffer, line, byte, text, len,
       +            &new_line, &new_byte
       +        );
       +        if (set_range_end) {
       +                cur_range.line2 = new_line;
       +                cur_range.byte2 = new_byte;
       +        }
       +        ins_range.line2 = new_line;
       +        ins_range.byte2 = new_byte;
       +        undo_push_insert(
       +            buffer->undo, &ins_buf, ins_range, cur_range, start_undo_group, mode
       +        );
       +        if (line_ret != NULL)
       +                *line_ret = new_line;
       +        if (byte_ret != NULL)
       +                *byte_ret = new_byte;
       +}
       +
       +void
       +buffer_insert_with_undo(
       +    ledit_buffer *buffer,
       +    ledit_range cur_range, int set_range_end,
       +    int start_undo_group, enum ledit_mode mode,
       +    size_t line, size_t byte,
       +    char *text, size_t len,
       +    size_t *line_ret, size_t *byte_ret) {
       +        buffer_insert_with_undo_base(
       +            buffer,
       +            cur_range, set_range_end,
       +            start_undo_group, mode,
       +            line, byte, text, len,
       +            line_ret, byte_ret
       +        );
       +        buffer_recalc_all_views_from_line(buffer, line);
        }
   DIR diff --git a/buffer.h b/buffer.h
       t@@ -5,7 +5,6 @@ typedef struct ledit_buffer ledit_buffer;
        
        #include "view.h"
        
       -/* FIXME: size_t for len, etc. */
        typedef struct {
                ledit_buffer *parent_buffer;
                char *text;      /* text, stored as gap buffer */
       t@@ -29,7 +28,7 @@ typedef struct {
        struct ledit_buffer {
                ledit_common *common;            /* common stuff, e.g. display, etc. */
                char *filename;                  /* last opened filename */
       -        undo_stack *undo;          /* undo manager */
       +        undo_stack *undo;                /* undo manager */
                ledit_buffer_marklist *marklist; /* list of mark positions set */
                ledit_line *lines;               /* array of lines */
                ledit_view **views;              /* array of registered views */
       t@@ -115,118 +114,6 @@ void buffer_destroy(ledit_buffer *buffer);
        void buffer_normalize_line(ledit_line *line);
        
        /*
       - * Insert 'src_len' bytes from 'src_line' starting at byte position 'src_index'
       - * into line 'dst_line' at byte position 'dst_index'.
       - * 'dst_line' must not be the same as 'src_line'.
       - * If 'text_ret' is not NULL, the copied text is additionally copied into 'text_ret'.
       - * This function does not update the views or normalize the lines, so it should
       - * only be used for efficiency purposes when performing multiple operations.
       - */
       -void buffer_insert_text_from_line_base(
       -    ledit_buffer *buffer,
       -    size_t dst_line, size_t dst_index,
       -    size_t src_line, size_t src_index, size_t src_len,
       -    txtbuf *text_ret
       -);
       -
       -/*
       - * Same as buffer_insert_text_from_line_base, but the views are updated afterwards.
       - */
       -void buffer_insert_text_from_line(
       -    ledit_buffer *buffer,
       -    size_t dst_line, size_t dst_index,
       -    size_t src_line, size_t src_index, size_t src_len,
       -    txtbuf *text_ret
       -);
       -
       -/*
       - * Insert text 'text' with length 'len' at line 'line_index' and byte position 'index'.
       - * The text must not contain newlines.
       - * This function does not update the views or normalize the lines, so it should
       - * only be used for efficiency purposes when performing multiple operations.
       - */
       -void buffer_insert_text_base(
       -    ledit_buffer *buffer,
       -    size_t line_index, size_t index,
       -    char *text, size_t len
       -);
       -
       -/*
       - * Same as buffer_insert_text_base, but the views are updated afterwards.
       - */
       -void buffer_insert_text(
       -    ledit_buffer *buffer,
       -    size_t line_index, size_t index,
       -    char *text, size_t len
       -);
       -
       -/* Insert text 'text' with length 'len' at line 'line_index' and byte position 'index.
       - * The text may contain newlines.
       - * If end_line_ret is not NULL, the line index at the end of the insertion is
       - * written into it.
       - * If end_byte_ret is not NULL, the byte position at the end of the insertion is
       - * written into it.
       - * This function does not update the views or normalize the lines, so it should
       - * only be used for efficiency purposes when performing multiple operations.
       - */
       -void buffer_insert_text_with_newlines_base(
       -    ledit_buffer *buffer,
       -    size_t line_index, size_t index,
       -    char *text, size_t len,
       -    size_t *end_line_ret, size_t *end_char_ret
       -);
       -
       -/*
       - * Same as buffer_insert_text_with_newlines_base, but the views are updated afterwards.
       - */
       -void buffer_insert_text_with_newlines(
       -    ledit_buffer *buffer,
       -    size_t line_index, size_t index,
       -    char *text, size_t len,
       -    size_t *end_line_ret, size_t *end_char_ret
       -);
       -
       -/*
       - * Append line after line at 'line_index'.
       - * if 'break_text' is not 0, the text on line 'line_index' starting at
       - * byte index 'text_index' is moved to the newly appended line.
       - * The views are notified that a line has been appended, but not told to update
       - * their line heights and offsets.
       - */
       -void buffer_append_line_base(ledit_buffer *buffer, size_t line_index, size_t text_index, int break_text);
       -
       -/*
       - * Same as buffer_append_line_base, but the views are told to update
       - * their line heights and offsets afterwards.
       - */
       -void buffer_append_line(ledit_buffer *buffer, size_t line_index, size_t text_index, int break_text);
       -
       -/*
       - * Delete lines between 'index1' and 'index2' (inclusive).
       - * The views are notified of the deletion but not told to
       - * update their line heights and offsets.
       - */
       -void buffer_delete_line_entries_base(ledit_buffer *buffer, size_t index1, size_t index2);
       -
       -/*
       - * Same as buffer_delete_line_entries_base, but the views are told to
       - * update their line heights and offsets.
       - */
       -void buffer_delete_line_entries(ledit_buffer *buffer, size_t index1, size_t index2);
       -
       -/*
       - * Convenience function to call buffer_delete_line_entries_base
       - * with two times the same line index.
       - */
       -void buffer_delete_line_entry_base(ledit_buffer *buffer, size_t index);
       -
       -/*
       - * Convenience function to call buffer_delete_line_entries
       - * with two times the same line index.
       - */
       -void buffer_delete_line_entry(ledit_buffer *buffer, size_t index);
       -
       -/*
         * Get the line at logical index 'index'.
         * The returned line is only valid until the next
         * action that appends or deletes line entries.
       t@@ -292,33 +179,87 @@ size_t line_prev_utf8(ledit_line *line, size_t index);
        
        /*
         * Get the unicode character index of a byte position.
       + * Note that the time complexity of this is linear.
         */
        size_t line_byte_to_char(ledit_line *line, size_t byte);
        
        /*
       - * Delete the section of line 'line' starting at byte 'start' with length 'length'
       - * and notify the views.
       - * Note that this does not tell the views to recalculate their line heights and offsets.
       + * Insert a mark with key 'mark' at line 'line' and byte 'byte'.
         */
       -void buffer_delete_line_section_base(ledit_buffer *buffer, size_t line, size_t start, size_t length);
       +void buffer_insert_mark(ledit_buffer *buffer, char *mark, size_t len, size_t line, size_t byte);
        
        /*
       - * Delete the unicode char at 'line_index' and 'byte_index' if 'dir' is >= 0,
       - * or the previous char if 'dir' is < 0.
       - * Returns the byte index where the deleted text was located.
       - * This function only notifies the views of the deletion, but does not tell
       - * them to recalculate their line heights and offsets.
       + * Perform one undo step.
       + * 'mode' should be the current mode of the calling view.
       + * 'cur_line' and 'cur_byte' are filled with the new line and cursor
       + * position after the undo.
         */
       -size_t buffer_delete_unicode_char_base(ledit_buffer *buffer, size_t line_index, size_t byte_index, int dir);
       +void buffer_undo(ledit_buffer *buffer, enum ledit_mode mode, size_t *cur_line, size_t *cur_byte);
        
        /*
       - * Same as buffer_delete_unicode_char_base, but the views are updated.
       + * Same as 'buffer_undo', but for redo.
         */
       -size_t buffer_delete_unicode_char(ledit_buffer *buffer, size_t line_index, size_t byte_index, int dir);
       +void buffer_redo(ledit_buffer *buffer, enum ledit_mode mode, size_t *cur_line, size_t *cur_byte);
        
        /*
       - * Insert a mark with key 'mark' at line 'line' and byte 'byte'.
       + * Delete the given range (which does not need to be sorted yet) and
       + * add the operation to the undo stack.
       + * 'cur_range' is the cursor range to be added to the undo stack.
       + * 'start_undo_group' and 'mode' are also used for the undo stack.
       + * If 'text_ret' is not NULL, the deleted text is written to it.
       + * This function does not tell the views to update their line heights
       + * and offsets.
         */
       -void buffer_insert_mark(ledit_buffer *buffer, char *mark, size_t len, size_t line, size_t byte);
       +void buffer_delete_with_undo_base(
       +    ledit_buffer *buffer, ledit_range cur_range,
       +    int start_undo_group, enum ledit_mode mode, /* for undo */
       +    size_t line_index1, size_t byte_index1,
       +    size_t line_index2, size_t byte_index2,
       +    txtbuf *text_ret
       +);
       +
       +/*
       + * Same as 'buffer_delete_with_undo_base', but the views are told to
       + * update their line heights and offsets afterwards.
       + */
       +void buffer_delete_with_undo(
       +    ledit_buffer *buffer, ledit_range cur_range,
       +    int start_undo_group, enum ledit_mode mode, /* for undo */
       +    size_t line_index1, size_t byte_index1,
       +    size_t line_index2, size_t byte_index2,
       +    txtbuf *text_ret
       +);
       +
       +/*
       + * Insert the given 'text' with length 'len' at line 'line' and
       + * byte index 'byte and add the operation to the undo stack.
       + * 'cur_range', 'start_undo_group', and 'mode' are used for the
       + * undo stack. If 'set_range_end' is set, the end position of
       + * 'cur_range' is set to the end position of the insertion before
       + * adding the operation to the undo stack.
       + * If 'line_ret' and 'byte_ret' are not NULL, they are filled with
       + * the end position of the insertion.
       + */
       +void buffer_insert_with_undo_base(
       +    ledit_buffer *buffer,
       +    ledit_range cur_range, int set_range_end,
       +    int start_undo_group, enum ledit_mode mode,
       +    size_t line, size_t byte,
       +    char *text, size_t len,
       +    size_t *line_ret, size_t *byte_ret
       +);
       +
       +/*
       + * Same as 'buffer_insert_with_undo_base', but the views are told to
       + * update their line heights and offsets afterwards.
       + */
       +void buffer_insert_with_undo(
       +    ledit_buffer *buffer,
       +    ledit_range cur_range, int set_range_end,
       +    int start_undo_group, enum ledit_mode mode,
       +    size_t line, size_t byte,
       +    char *text, size_t len,
       +    size_t *line_ret, size_t *byte_ret
       +);
        
        #endif
   DIR diff --git a/keys_basic.c b/keys_basic.c
       t@@ -37,7 +37,7 @@
        #include "keys_command.h"
        #include "keys_basic_config.h"
        
       -/* note: this is supposed to be global for all buffers */
       +/* note: this is supposed to be global for all views/buffers */
        int paste_buffer_line_based = 0;
        static txtbuf *paste_buffer = NULL;
        static int last_lines_scrolled = -1;
       t@@ -77,7 +77,6 @@ static struct {
        } key_stack = {0, 0, NULL};
        
        static struct action (*grab_char_cb)(ledit_view *view, char *text, size_t len) = NULL;
       -static int hard_line_based = 1;
        
        void
        basic_key_cleanup(void) {
       t@@ -334,7 +333,7 @@ static void
        get_new_line_softline(
            ledit_view *view, size_t cur_line, size_t cur_index, int movement,
            size_t *new_line_ret, int *new_softline_ret) {
       -        if (hard_line_based) {
       +        if (view->buffer->hard_line_based) {
                        if (movement < 0 && (size_t)-movement > cur_line)
                                *new_line_ret = 0;
                        else
       t@@ -403,31 +402,21 @@ delete_range(
                (void)selected; /* FIXME */
                if (copy_to_buffer && !paste_buffer)
                        paste_buffer = txtbuf_new();
       -        txtbuf *buf = copy_to_buffer ? paste_buffer : txtbuf_new();
       -        ledit_range cur_range, del_range;
       -        cur_range.line1 = view->cur_line;
       -        cur_range.byte1 = view->cur_index;
       +        txtbuf *buf = copy_to_buffer ? paste_buffer : NULL;
                enum delete_mode delmode = DELETE_CHAR;
                if (line_based) {
       -                if (hard_line_based)
       +                if (view->buffer->hard_line_based)
                                delmode = DELETE_HARDLINE;
                        else
                                delmode = DELETE_SOFTLINE;
                }
                view_delete_range(
       -            view, delmode,
       +            view, delmode, 1,
                    line_index1, byte_index1,
                    line_index2, byte_index2,
                    &view->cur_line, &view->cur_index,
       -            &del_range, buf
       +            buf
                );
       -        cur_range.line2 = view->cur_line;
       -        cur_range.byte2 = view->cur_index;
       -        undo_push_delete(
       -            view->buffer->undo, buf, del_range, cur_range, 1, view->mode
       -        );
       -        if (!copy_to_buffer)
       -                txtbuf_destroy(buf);
        }
        
        /* FIXME: better interface for this; documentation */
       t@@ -439,8 +428,7 @@ insert_text(
            size_t cur_line1, size_t cur_index1,
            size_t cur_line2, size_t cur_index2,
            int set_range_start, int set_range_end, int start_group) {
       -        txtbuf ins_buf = {.text = text, .len = len, .cap = len};
       -        ledit_range cur_range, del_range;
       +        ledit_range cur_range;
                if (set_range_start) {
                        cur_range.line1 = cur_line1;
                        cur_range.byte1 = cur_index1;
       t@@ -448,27 +436,24 @@ insert_text(
                        cur_range.line1 = view->cur_line;
                        cur_range.byte1 = view->cur_index;
                }
       -        del_range.line1 = line;
       -        del_range.byte1 = index;
       -        size_t cur_line, cur_index;
       -        buffer_insert_text_with_newlines(
       -            view->buffer, line, index, text, len,
       -            &cur_line, &cur_index
       -        );
                /* this is mainly for pasting, where the new line and index
                   should not be at the end of the pasted text */
                if (set_range_end) {
       -                cur_range.line2 = view->cur_line = cur_line2;
       -                cur_range.byte2 = view->cur_index = cur_index2;
       -        } else {
       -                cur_range.line2 = view->cur_line = cur_line;
       -                cur_range.byte2 = view->cur_index = cur_index;
       +                cur_range.line2 = cur_line2;
       +                cur_range.byte2 = cur_index2;
                }
       -        del_range.line2 = cur_line;
       -        del_range.byte2 = cur_index;
       -        undo_push_insert(
       -            view->buffer->undo, &ins_buf, del_range, cur_range, start_group, view->mode
       +        /* FIXME: why did I ever decide to make set_range_end
       +           mean exactly the opposite for the two functions? */
       +        buffer_insert_with_undo(
       +            view->buffer, cur_range, !set_range_end,
       +            start_group, view->mode,
       +            line, index, text, len,
       +            &view->cur_line, &view->cur_index
                );
       +        if (set_range_end) {
       +                view->cur_line = cur_line2;
       +                view->cur_index = cur_index2;
       +        }
        }
        
        static int
       t@@ -562,7 +547,7 @@ append_line_above(ledit_view *view, char *text, size_t len) {
                /* do this here already so the mode group is the same for the newline insertion */
                enter_insert(view, text, len);
                view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start, &end);
       -        if (hard_line_based || start == 0) {
       +        if (view->buffer->hard_line_based || start == 0) {
                        insert_text(view, view->cur_line, 0, "\n", 1, 0, 0, view->cur_line, 0, 0, 1, 1);
                } else {
                        /* FIXME: this interface really is horrible */
       t@@ -577,7 +562,7 @@ append_line_below(ledit_view *view, char *text, size_t len) {
                enter_insert(view, text, len);
                view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start, &end);
                ledit_line *ll = buffer_get_line(view->buffer, view->cur_line);
       -        if (hard_line_based || end == ll->len) {
       +        if (view->buffer->hard_line_based || end == ll->len) {
                        insert_text(view, view->cur_line, ll->len, "\n", 1, 0, 0, view->cur_line + 1, 0, 0, 1, 1);
                } else {
                        insert_text(view, view->cur_line, end, "\n\n", 2, 0, 0, view->cur_line + 1, 0, 0, 1, 1);
       t@@ -604,7 +589,7 @@ append_after_eol(ledit_view *view, char *text, size_t len) {
                /* make cursor jump back to original position on undo */
                push_undo_empty_insert(view, view->cur_line, view->cur_index, 1);
                ledit_line *ll = buffer_get_line(view->buffer, view->cur_line);
       -        if (hard_line_based)
       +        if (view->buffer->hard_line_based)
                        view->cur_index = ll->len;
                else
                        view->cur_index = end;
       t@@ -852,7 +837,7 @@ delete_to_eol(ledit_view *view, char *text, size_t len) {
                        return err_invalid_key(view);
                size_t start, end;
                ledit_line *ll = buffer_get_line(view->buffer, view->cur_line);
       -        if (hard_line_based) {
       +        if (view->buffer->hard_line_based) {
                        end = ll->len;
                } else {
                        view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start, &end);
       t@@ -879,7 +864,7 @@ change_to_eol(ledit_view *view, char *text, size_t len) {
                view_set_mode(view, INSERT);
                size_t start, end;
                ledit_line *ll = buffer_get_line(view->buffer, view->cur_line);
       -        if (hard_line_based) {
       +        if (view->buffer->hard_line_based) {
                        end = ll->len;
                } else {
                        view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start, &end);
       t@@ -942,11 +927,11 @@ change_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type) {
                /* this hackery is needed to avoid deleting the entire last line and
                   instead leave an empty line - this should be made nicer (FIXME) */
                size_t pos1 = view->cur_index, pos2 = char_pos;
       -        if (line_based && !hard_line_based) {
       +        if (line_based && !view->buffer->hard_line_based) {
                        size_t pos1, pos2, tmp;
                        view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &pos1, &tmp);
                        view_get_pos_softline_bounds(view, line, char_pos, &tmp, &pos2);
       -        } else if (line_based && hard_line_based) {
       +        } else if (line_based && view->buffer->hard_line_based) {
                        pos1 = 0;
                        ledit_line *ll = buffer_get_line(view->buffer, line);
                        pos2 = ll->len;
       t@@ -1046,7 +1031,7 @@ yank_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type) {
                        swap_sz(&l1, &l2);
                        swap_sz(&b1, &b2);
                }
       -        if (line_based && !hard_line_based) {
       +        if (line_based && !view->buffer->hard_line_based) {
                        size_t start1, end2, tmp;
                        view_get_pos_softline_bounds(view, l1, b1, &start1, &tmp);
                        view_get_pos_softline_bounds(view, l2, b2, &tmp, &end2);
       t@@ -1058,7 +1043,7 @@ yank_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type) {
                        buffer_copy_text_to_txtbuf(
                            view->buffer, paste_buffer, l1, start1, l2, end2
                        );
       -        } else if (line_based && hard_line_based) {
       +        } else if (line_based && view->buffer->hard_line_based) {
                        ledit_line *ll = buffer_get_line(view->buffer, l2);
                        size_t end = ll->len;
                        if (l2 < view->lines_num - 1) {
       t@@ -1119,6 +1104,7 @@ delete(ledit_view *view, char *text, size_t len) {
        /* FIXME: should this get number of lines to remove or actual end line? */
        static void
        delete_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type) {
       +        view_wipe_line_cursor_attrs(view, view->cur_line);
                int line_based = type == KEY_MOTION_LINE ? 1 : 0;
                delete_range(
                    view, line_based, 0,
       t@@ -1147,12 +1133,14 @@ paste_normal(ledit_view *view, char *text, size_t len) {
                        view_wipe_line_cursor_attrs(view, view->cur_line);
                        ledit_line *ll = buffer_get_line(view->buffer, view->cur_line);
                        size_t brk = 0;
       -                if (hard_line_based) {
       +                if (view->buffer->hard_line_based) {
                                brk = ll->len;
                        } else {
                                size_t tmp;
                                view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &tmp, &brk);
                        }
       +                /* FIXME: this is a bit inefficient because insert_text does not
       +                   use the *_base functions, but maybe this way is a bit safer */
                        insert_text(
                            view, view->cur_line, brk,
                            "\n", 1, 0, 0, view->cur_line, view->cur_index, 0, 1, 1
       t@@ -1203,7 +1191,7 @@ paste_normal_backwards(ledit_view *view, char *text, size_t len) {
                        view_wipe_line_cursor_attrs(view, view->cur_line);
                        ledit_line *ll = buffer_get_line(view->buffer, view->cur_line);
                        size_t brk = 0;
       -                if (!hard_line_based) {
       +                if (!view->buffer->hard_line_based) {
                                size_t tmp;
                                view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &brk, &tmp);
                        }
       t@@ -1414,7 +1402,7 @@ move_to_eol(ledit_view *view, char *text, size_t len) {
                );
                ledit_line *ll = buffer_get_line(view->buffer, new_line);
                size_t end_index = ll->len;
       -        if (!hard_line_based) {
       +        if (!view->buffer->hard_line_based) {
                        size_t tmp;
                        view_get_softline_bounds(view, new_line, new_softline, &tmp, &end_index);
                }
       t@@ -1683,11 +1671,6 @@ join_lines(ledit_view *view, char *text, size_t len) {
                int start_group = 1;
                ledit_line *ll1;
                size_t cur_line = view->cur_line;
       -        /* FIXME: have a general tmp buf for everyone to use */
       -        txtbuf *buf = txtbuf_new();
       -        size_t oldlen;
       -        ledit_range cur_range, del_range;
       -        cur_range.line1 = cur_range.line2 = cur_line;
                for (int i = 0; i < num; i++) {
                        if (cur_line == view->lines_num - 1)
                                break;
       t@@ -1695,26 +1678,18 @@ join_lines(ledit_view *view, char *text, size_t len) {
                           I'll just leave it in case I change the way lines
                           are stored later */
                        ll1 = buffer_get_line(view->buffer, cur_line);
       -                oldlen = ll1->len;
                        /* FIXME: truncate whitespace to one space */
       -                view_delete_range(
       -                    view, DELETE_CHAR,
       +                view_delete_range_base(
       +                    view, DELETE_CHAR, start_group,
                            cur_line, ll1->len, cur_line + 1, 0,
       -                    NULL, NULL, &del_range, buf
       -                );
       -                cur_range.byte1 = view->cur_index;
       -                cur_range.byte2 = view->cur_index =
       -                    view_get_legal_normal_pos(view, view->cur_line, oldlen);
       -                undo_push_delete(
       -                    view->buffer->undo, buf, del_range, cur_range,
       -                    start_group, view->mode
       +                    &view->cur_line, &view->cur_index, NULL
                        );
                        start_group = 0;
                }
       +        buffer_recalc_all_views_from_line(view->buffer, cur_line);
                view_set_line_cursor_attrs(
                    view, view->cur_line, view->cur_index
                );
       -        txtbuf_destroy(buf);
                finalize_repetition_stack();
                return (struct action){ACTION_NONE, NULL};
        }
       t@@ -1725,7 +1700,7 @@ insert_at_beginning(ledit_view *view, char *text, size_t len) {
                        return err_invalid_key(view);
                enter_insert(view, text, len);
                size_t new_index = 0;
       -        if (!hard_line_based) {
       +        if (!view->buffer->hard_line_based) {
                        size_t tmp;
                        view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &new_index, &tmp);
                }
       t@@ -1744,7 +1719,7 @@ cursor_to_first_non_ws(ledit_view *view, char *text, size_t len) {
                if (num != 0)
                        return err_invalid_key(view);
                size_t new_index = 0;
       -        if (hard_line_based) {
       +        if (view->buffer->hard_line_based) {
                        new_index = view_line_next_non_whitespace(view, view->cur_line, 0);
                } else {
                        size_t start, end;
       t@@ -1779,7 +1754,7 @@ cursor_to_beginning(ledit_view *view, char *text, size_t len) {
                        return err_invalid_key(view);
                /* FIXME: should anything be done with num? */
                size_t start_index = 0;
       -        if (!hard_line_based) {
       +        if (!view->buffer->hard_line_based) {
                        size_t tmp;
                        view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start_index, &tmp);
                }
   DIR diff --git a/keys_command.c b/keys_command.c
       t@@ -162,44 +162,34 @@ handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2) {
                        size_t rlen = strlen(last_replacement);
                        txtbuf *buf = txtbuf_new(); /* FIXME: don't allocate new every time */
                        view_wipe_line_cursor_attrs(view, view->cur_line);
       +                size_t min_line = SIZE_MAX;
                        for (size_t i = l1 - 1; i < l2; i++) {
                                ledit_line *ll = buffer_get_line(view->buffer, i);
                                buffer_normalize_line(ll);
                                char *pos = strstr(ll->text, last_search);
       +                        if (pos != NULL && i < min_line)
       +                                min_line = i;
                                while (pos != NULL) {
                                        size_t index = (size_t)(pos - ll->text);
       -                                ledit_range cur_range, del_range;
       +                                ledit_range cur_range;
                                        cur_range.line1 = view->cur_line;
                                        cur_range.byte1 = view->cur_line;
       -                                view_delete_range(
       -                                    view, DELETE_CHAR,
       -                                    i, index,
       -                                    i, index + slen,
       -                                    &view->cur_line, &view->cur_index,
       -                                    &del_range, buf
       -                                );
       -                                cur_range.line2 = view->cur_line;
       -                                cur_range.byte2 = view->cur_index;
       -                                undo_push_delete(
       -                                    view->buffer->undo, buf, del_range, cur_range, start_undo_group, view->mode
       +                                cur_range.line2 = i;
       +                                cur_range.byte2 = index;
       +                                buffer_delete_with_undo_base(
       +                                    view->buffer, cur_range,
       +                                    start_undo_group, view->mode,
       +                                    i, index, i, index + slen, NULL
                                        );
       +                                view->cur_line = i;
       +                                view->cur_index = index;
                                        start_undo_group = 0;
       -                                txtbuf ins_buf = {.text = last_replacement, .len = rlen, .cap = rlen};
       -                                cur_range.line1 = view->cur_line;
       -                                cur_range.byte1 = view->cur_index;
       -                                del_range.line1 = i;
       -                                del_range.byte1 = index;
       -                                size_t cur_line, cur_index;
       -                                buffer_insert_text_with_newlines(
       -                                    view->buffer, i, index, last_replacement, rlen,
       -                                    &cur_line, &cur_index
       -                                );
       -                                cur_range.line2 = view->cur_line;
       -                                cur_range.byte2 = view->cur_index;
       -                                del_range.line2 = cur_line;
       -                                del_range.byte2 = cur_index;
       -                                undo_push_insert(
       -                                    view->buffer->undo, &ins_buf, del_range, cur_range, 0, view->mode
       +                                cur_range.line1 = i;
       +                                cur_range.byte1 = index;
       +                                buffer_insert_with_undo_base(
       +                                    view->buffer, cur_range, 0, 0, view->mode,
       +                                    i, index, last_replacement, rlen,
       +                                    NULL, NULL
                                        );
                                        num++;
                                        if (!global) break;
       t@@ -207,6 +197,7 @@ handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2) {
                                        pos = strstr(ll->text + index + rlen, last_search);
                                }
                        }
       +                buffer_recalc_all_views_from_line(view->buffer, min_line);
                        /* FIXME: show number replaced */
                        /* this doesn't need to be added to the undo stack since it's called on undo/redo anyways */
                        view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view->cur_index);
   DIR diff --git a/undo.c b/undo.c
       t@@ -267,3 +267,9 @@ ledit_redo(undo_stack *undo, enum ledit_mode mode, void *callback_data,
                *min_line_ret = min_line;
                return UNDO_NORMAL;
        }
       +
       +void
       +undo_change_last_cur_range(undo_stack *undo, ledit_range cur_range) {
       +        undo_elem *e = peek_undo_elem(undo);
       +        e->cursor_range = cur_range;
       +}
   DIR diff --git a/undo.h b/undo.h
       t@@ -106,3 +106,12 @@ undo_status ledit_redo(
            void *callback_data, undo_insert_callback insert_cb, undo_delete_callback delete_cb,
            size_t *cur_line_ret, size_t *cur_index_ret, size_t *min_line_ret
        );
       +
       +/*
       + * Change the cursor range stored for the last operation.
       + * This is sort of a hack used by view_delete_range and some other
       + * functions to set the cursor range later if it isn't known yet before
       + * inserting/deleting text.
       + * Fails silently if the stack is empty.
       + */
       +void undo_change_last_cur_range(undo_stack *undo, ledit_range cur_range);
   DIR diff --git a/view.c b/view.c
       t@@ -188,14 +188,23 @@ resize_and_move_line_gap(ledit_view *view, size_t min_size, size_t index) {
                );
        }
        
       +/* Checking vl->cursor_index_valid in these notify functions is needed
       +   to avoid re-setting the cursor index for lines that were wiped but
       +   where the line/index of the view hasn't been updated yet (e.g. when
       +   the current line is wiped, then view_delete_range is called to delete
       +   a part, which may cause these notification functions to be called) */
        void
        view_notify_insert_text(ledit_view *view, size_t line, size_t index, size_t len) {
                ledit_view_line *vl = view_get_line(view, line);
                vl->text_dirty = 1;
                if (line == view->cur_line && index < view->cur_index) {
                        view->cur_index += len;
       -                view_set_line_cursor_attrs(view, line, view->cur_index);
       +                ledit_view_line *vl = view_get_line(view, line);
       +                if (vl->cursor_index_valid)
       +                        view_set_line_cursor_attrs(view, line, view->cur_index);
                }
       +        /* FIXME: maybe just wipe selection completely, or at least
       +           when in insert mode? */
                if (view->sel_valid)
                        view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index);
        }
       t@@ -216,7 +225,9 @@ view_notify_delete_text(ledit_view *view, size_t line, size_t index, size_t len)
                                    view, view->cur_line, view->cur_index
                                );
                        }
       -                view_set_line_cursor_attrs(view, line, view->cur_index);
       +                ledit_view_line *vl = view_get_line(view, line);
       +                if (vl->cursor_index_valid)
       +                        view_set_line_cursor_attrs(view, line, view->cur_index);
                }
                if (view->sel_valid)
                        view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index);
       t@@ -255,6 +266,9 @@ view_notify_delete_lines(ledit_view *view, size_t index1, size_t index2) {
                                view->cur_line = 0;
                                view->cur_index = 0;
                        }
       +                ledit_view_line *vl = view_get_line(view, view->cur_line);
       +                if (vl->cursor_index_valid)
       +                        view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
                }
                if (view->sel_valid)
                        view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index);
       t@@ -1132,22 +1146,23 @@ view_get_legal_normal_pos(ledit_view *view, size_t line, size_t pos) {
        
        void
        view_delete_range(
       -    ledit_view *view, enum delete_mode delmode,
       +    ledit_view *view,
       +    enum delete_mode delmode, int start_undo_group,
            size_t line_index1, size_t byte_index1,
            size_t line_index2, size_t byte_index2,
            size_t *new_line_ret, size_t *new_byte_ret,
       -    ledit_range *final_range_ret, txtbuf *text_ret) {
       +    txtbuf *text_ret) {
                view_delete_range_base(
       -            view, delmode,
       +            view,
       +            delmode, start_undo_group,
                    line_index1, byte_index1,
                    line_index2, byte_index2,
                    new_line_ret, new_byte_ret,
       -            final_range_ret, text_ret
       +            text_ret
                );
                /* need to start recalculating one line before in case first
                   line was deleted and offset is now wrong */
                size_t min = line_index1 < line_index2 ? line_index1 : line_index2;
       -        /* FIXME: a bit ugly to do this here */
                buffer_recalc_all_views_from_line(
                    view->buffer, min > 0 ? min - 1 : min
                );
       t@@ -1162,29 +1177,31 @@ view_delete_range(
           given, but I couldn't reproduce this bug */
        void
        view_delete_range_base(
       -    ledit_view *view, enum delete_mode delmode,
       +    ledit_view *view,
       +    enum delete_mode delmode, int start_undo_group,
            size_t line_index1, size_t byte_index1,
            size_t line_index2, size_t byte_index2,
            size_t *new_line_ret, size_t *new_byte_ret,
       -    ledit_range *final_range_ret, txtbuf *text_ret) {
       +    txtbuf *text_ret) {
                /* FIXME: Oh boy, this is nasty */
                /* range line x, range byte x */
                size_t rgl1 = 0, rgb1 = 0, rgl2 = 0, rgb2 = 0;
       +        /* line_index1 and byte_index1 are used as cursor start position
       +           -> FIXME: why not just use view->cur_line, view->cur_index here? */
       +        size_t cur_line = line_index1;
       +        size_t cur_byte = byte_index1;
       +        view_sort_selection(&line_index1, &byte_index1, &line_index2, &byte_index2);
                size_t new_line = 0, new_byte = 0;
                assert(line_index1 < view->lines_num);
                assert(line_index2 < view->lines_num);
       +        ledit_range cur_range = {0, 0, 0, 0};
                /* FIXME: could this be simplified by just calculating the range and then using
                   the non-line-based version? */
                if (delmode == DELETE_HARDLINE) {
                        int x, sl_useless;
                        size_t l1 = line_index1, l2 = line_index2;
       -                if (line_index1 > line_index2) {
       -                        l1 = line_index2;
       -                        l2 = line_index1;
       -                }
       -                size_t dell1 = l1, dell2 = l2;
       -                ledit_line *ll = buffer_get_line(view->buffer, line_index1);
       -                view_pos_to_x_softline(view, line_index1, byte_index1, &x, &sl_useless);
       +                ledit_line *ll;
       +                view_pos_to_x_softline(view, cur_line, cur_byte, &x, &sl_useless);
                        if (l1 > 0 && l2 < view->lines_num - 1) {
                                rgl1 = l1;
                                rgb1 = 0;
       t@@ -1209,13 +1226,6 @@ view_delete_range_base(
                                ll = buffer_get_line(view->buffer, rgl2);
                                rgb2 = ll->len;
                        }
       -                if (text_ret) {
       -                        buffer_copy_text_to_txtbuf(
       -                            view->buffer, text_ret,
       -                            rgl1, rgb1, rgl2, rgb2
       -                        );
       -                }
       -                /* default is dell1 = l1, dell2 = l2 */
                        if (l2 < view->lines_num - 1) {
                                new_line = l1;
                                new_byte = view_x_softline_to_pos(
       t@@ -1227,30 +1237,24 @@ view_delete_range_base(
                                    view, l1 - 1, x, 0
                                );
                        } else {
       -                        dell1 = l1 + 1;
       -                        dell2 = l2;
       -                        new_line = l1;
       +                        new_line = 0;
                                new_byte = 0;
       -                        /* happens when all lines are deleted, so one line has to be cleared */
       -                        ll = buffer_get_line(view->buffer, l1);
       -                        buffer_delete_line_section_base(
       -                            view->buffer, l1, 0, ll->len
       -                        );
       -                }
       -                if (dell1 <= dell2) {
       -                        buffer_delete_line_entries_base(view->buffer, dell1, dell2);
                        }
       +                buffer_delete_with_undo_base(
       +                    view->buffer, cur_range,
       +                    start_undo_group, view->mode,
       +                    rgl1, rgb1, rgl2, rgb2, text_ret
       +                );
                } else if (delmode == DELETE_SOFTLINE) {
       -                int x, softline1, softline2;
       -                ledit_line *line1 = buffer_get_line(view->buffer, line_index1);
       -                ledit_view_line *vline1 = view_get_line(view, line_index1);
       -                view_pos_to_x_softline(view, line_index1, byte_index1, &x, &softline1);
       +                int x, sl_useless;
       +                view_pos_to_x_softline(view, cur_line, cur_byte, &x, &sl_useless);
                        if (line_index1 == line_index2) {
       -                        int x_useless;
       +                        int x_useless, l1, l2;
       +                        ledit_line *line1 = buffer_get_line(view->buffer, line_index1);
       +                        ledit_view_line *vline1 = view_get_line(view, line_index1);
       +                        view_pos_to_x_softline(view, line_index1, byte_index1, &x_useless, &l1);
       +                        view_pos_to_x_softline(view, line_index2, byte_index2, &x_useless, &l2);
                                PangoLayout *layout = get_pango_layout(view, line_index1);
       -                        pango_layout_index_to_line_x(layout, byte_index2, 0, &softline2, &x_useless);
       -                        int l1 = softline1 < softline2 ? softline1 : softline2;
       -                        int l2 = softline1 < softline2 ? softline2 : softline1;
                                PangoLayoutLine *pl1 = pango_layout_get_line_readonly(layout, l1);
                                PangoLayoutLine *pl2 = pango_layout_get_line_readonly(layout, l2);
                                /* don't delete entire line if it is the last one remaining */
       t@@ -1258,11 +1262,9 @@ view_delete_range_base(
                                        if (line_index1 < view->lines_num - 1) {
                                                /* cursor can be moved to next hard line */
                                                new_line = line_index1;
       -                                        size_t tmp_byte;
       -                                        tmp_byte = view_x_softline_to_pos(
       +                                        new_byte = view_x_softline_to_pos(
                                                    view, line_index1 + 1, x, 0
                                                );
       -                                        new_byte = (size_t)tmp_byte;
                                                rgl1 = line_index1;
                                                rgb1 = 0;
                                                rgl2 = line_index1 + 1;
       t@@ -1283,50 +1285,41 @@ view_delete_range_base(
                                                rgl2 = line_index1;
                                                rgb2 = line1->len;
                                        }
       -                                if (text_ret) {
       -                                        buffer_copy_text_to_txtbuf(
       -                                            view->buffer, text_ret,
       -                                            rgl1, rgb1,
       -                                            rgl2, rgb2
       -                                        );
       -                                }
       -                                buffer_delete_line_entry_base(view->buffer, line_index1);
       +                                buffer_delete_with_undo_base(
       +                                    view->buffer, cur_range,
       +                                    start_undo_group, view->mode,
       +                                    rgl1, rgb1, rgl2, rgb2, text_ret
       +                                );
                                } else {
                                        assert(pl2->start_index + pl2->length >= pl1->start_index);
                                        rgl1 = rgl2 = line_index1;
                                        rgb1 = (size_t)pl1->start_index;
                                        rgb2 = (size_t)(pl2->start_index + pl2->length);
       -                                if (text_ret) {
       -                                        buffer_copy_text_to_txtbuf(
       -                                            view->buffer, text_ret,
       -                                            rgl1, rgb1,
       -                                            rgl2, rgb2
       -                                        );
       -                                }
       -                                buffer_delete_line_section_base(
       -                                    view->buffer, line_index1, rgb1, rgb2 - rgb1
       +                                /* the deletion has to happen before calculating the new cursor
       +                                   position because deleting softlines could change the line
       +                                   wrapping as well */
       +                                buffer_delete_with_undo_base(
       +                                    view->buffer, cur_range,
       +                                    start_undo_group, view->mode,
       +                                    rgl1, rgb1, rgl2, rgb2, text_ret
                                        );
       -                                if (l2 == vline1->softlines - 1 && line_index1 < view->lines_num - 1) {
       +                                set_pango_text_and_highlight(view, line_index1);
       +                                vline1 = view_get_line(view, line_index1);
       +                                if (l1 == vline1->softlines && line_index1 < view->lines_num - 1) {
                                                new_line = line_index1 + 1;
       -                                        size_t tmp_byte;
       -                                        tmp_byte = view_x_softline_to_pos(
       +                                        new_byte = view_x_softline_to_pos(
                                                    view, line_index1 + 1, x, 0
                                                );
       -                                        new_byte = (size_t)tmp_byte;
       -                                } else if (l2 < vline1->softlines - 1) {
       +                                } else if (l1 <= vline1->softlines - 1) {
                                                new_line = line_index1;
       -                                        size_t tmp_byte;
       -                                        tmp_byte = view_x_softline_to_pos(
       +                                        new_byte = view_x_softline_to_pos(
                                                    view, line_index1, x, l1
                                                );
       -                                        new_byte = (size_t)tmp_byte;
                                        } else if (l1 > 0) {
                                                new_line = line_index1;
       -                                        size_t tmp_byte;
       -                                        tmp_byte = view_x_softline_to_pos(
       +                                        new_byte = view_x_softline_to_pos(
                                                    view, line_index1, x, l1 - 1
                                                );
       -                                        new_byte = (size_t)tmp_byte;
                                        } else {
                                                /* the line has been emptied and is the last line remaining */
                                                new_line = 0;
       t@@ -1335,19 +1328,8 @@ view_delete_range_base(
                                }
                        } else {
                                int x_useless, sl1, sl2;
       -                        size_t l1, l2, b1, b2;
       -                        if (line_index1 < line_index2) {
       -                                l1 = line_index1;
       -                                b1 = byte_index1;
       -                                l2 = line_index2;
       -                                b2 = byte_index2;
       -                        } else {
       -                                l1 = line_index2;
       -                                b1 = byte_index2;
       -                                l2 = line_index1;
       -                                b2 = byte_index1;
       -                        }
       -                        ledit_line *ll1 = buffer_get_line(view->buffer, l1);
       +                        size_t l1 = line_index1, b1 = byte_index1;
       +                        size_t l2 = line_index2, b2 = byte_index2;
                                ledit_line *ll2 = buffer_get_line(view->buffer, l2);
                                ledit_view_line *vl1 = view_get_line(view, l1);
                                ledit_view_line *vl2 = view_get_line(view, l2);
       t@@ -1363,15 +1345,6 @@ view_delete_range_base(
                                                rgl2 = l2;
                                                rgb1 = 0;
                                                rgb2 = ll2->len;
       -                                        if (text_ret) {
       -                                                buffer_copy_text_to_txtbuf(
       -                                                    view->buffer, text_ret,
       -                                                    rgl1, rgb1,
       -                                                    rgl2, rgb2
       -                                                );
       -                                        }
       -                                        buffer_delete_line_section_base(view->buffer, l1, 0, ll1->len);
       -                                        buffer_delete_line_entries_base(view->buffer, l1 + 1, l2);
                                                new_line = 0;
                                                new_byte = 0;
                                        } else {
       t@@ -1396,31 +1369,24 @@ view_delete_range_base(
                                                        rgl2 = l2 + 1;
                                                        rgb2 = 0;
                                                }
       -                                        if (text_ret) {
       -                                                buffer_copy_text_to_txtbuf(
       -                                                    view->buffer, text_ret,
       -                                                    rgl1, rgb1,
       -                                                    rgl2, rgb2
       -                                                );
       -                                        }
       -                                        buffer_delete_line_entries_base(view->buffer, l1, l2);
                                        }
       +                                buffer_delete_with_undo_base(
       +                                    view->buffer, cur_range,
       +                                    start_undo_group, view->mode,
       +                                    rgl1, rgb1, rgl2, rgb2, text_ret
       +                                );
                                } else if (sl1 == 0) {
                                        rgl1 = l1;
                                        rgb1 = 0;
                                        rgl2 = l2;
                                        rgb2 = (size_t)(pl2->start_index + pl2->length);
       -                                if (text_ret) {
       -                                        buffer_copy_text_to_txtbuf(
       -                                            view->buffer, text_ret,
       -                                            rgl1, rgb1,
       -                                            rgl2, rgb2
       -                                        );
       -                                }
       -                                buffer_delete_line_section_base(view->buffer, l2, 0, rgb2);
       +                                buffer_delete_with_undo_base(
       +                                    view->buffer, cur_range,
       +                                    start_undo_group, view->mode,
       +                                    rgl1, rgb1, rgl2, rgb2, text_ret
       +                                );
                                        new_line = l1;
       -                                new_byte = view_x_softline_to_pos(view, l2, x, 0);
       -                                buffer_delete_line_entries_base(view->buffer, l1, l2 - 1);
       +                                new_byte = view_x_softline_to_pos(view, l1, x, 0);
                                } else if (sl2 == vl2->softlines - 1) {
                                        rgl1 = l1;
                                        rgb1 = (size_t)pl1->start_index;
       t@@ -1435,38 +1401,27 @@ view_delete_range_base(
                                                    view, l2 + 1, x, 0
                                                );
                                        }
       -                                if (text_ret) {
       -                                        buffer_copy_text_to_txtbuf(
       -                                            view->buffer, text_ret,
       -                                            rgl1, rgb1,
       -                                            rgl2, rgb2
       -                                        );
       -                                }
       -                                buffer_delete_line_section_base(view->buffer, l1, rgb1, ll1->len - rgb1);
       -                                buffer_delete_line_entries_base(view->buffer, l1 + 1, l2);
       +                                buffer_delete_with_undo_base(
       +                                    view->buffer, cur_range,
       +                                    start_undo_group, view->mode,
       +                                    rgl1, rgb1, rgl2, rgb2, text_ret
       +                                );
                                } else {
       -                                /* FIXME: this could be made nicer by just using the range to
       -                                   delete all in one go at the end */
                                        rgl1 = l1;
                                        rgb1 = (size_t)pl1->start_index;
                                        rgl2 = l2;
                                        rgb2 = (size_t)(pl2->start_index + pl2->length);
       -                                if (text_ret) {
       -                                        buffer_copy_text_to_txtbuf(
       -                                            view->buffer, text_ret,
       -                                            rgl1, rgb1,
       -                                            rgl2, rgb2
       -                                        );
       -                                }
       -                                buffer_delete_line_section_base(view->buffer, l1, rgb1, ll1->len - rgb1);
       -                                buffer_insert_text_from_line_base(
       -                                    view->buffer,
       -                                    l1, rgb1, l2, rgb2,
       -                                    ll2->len - rgb2, NULL
       +                                buffer_delete_with_undo_base(
       +                                    view->buffer, cur_range,
       +                                    start_undo_group, view->mode,
       +                                    rgl1, rgb1, rgl2, rgb2, text_ret
                                        );
       -                                buffer_delete_line_entries_base(view->buffer, l1 + 1, l2);
                                        new_line = l1;
       +                                /* important so vl1->softlines is updated */
                                        set_pango_text_and_highlight(view, l1);
       +                                /* important because line pointers may only stay
       +                                   valid until something is deleted or inserted */
       +                                vl1 = view_get_line(view, l1);
                                        /* it's technically possible that the remaining part of the
                                           second line is so small that it doesn't generate a new
                                           softline, so there needs to be a special case - this is
       t@@ -1479,64 +1434,26 @@ view_delete_range_base(
                                }
                        }
                } else {
       -                if (line_index1 == line_index2) {
       -                        rgl1 = rgl2 = line_index1;
       -                        if (byte_index1 < byte_index2) {
       -                                rgb1 = byte_index1;
       -                                rgb2 = byte_index2;
       -                        } else {
       -                                rgb1 = byte_index2;
       -                                rgb2 = byte_index1;
       -                        }
       -                        if (text_ret) {
       -                                buffer_copy_text_to_txtbuf(
       -                                    view->buffer, text_ret,
       -                                    rgl1, rgb1,
       -                                    rgl2, rgb2
       -                                );
       -                        }
       -                        buffer_delete_line_section_base(view->buffer, line_index1, rgb1, rgb2 - rgb1);
       -                        new_line = line_index1;
       -                        new_byte = rgb1;
       -                } else {
       -                        if (line_index1 < line_index2) {
       -                                rgl1 = line_index1;
       -                                rgb1 = byte_index1;
       -                                rgl2 = line_index2;
       -                                rgb2 = byte_index2;
       -                        } else {
       -                                rgl1 = line_index2;
       -                                rgb1 = byte_index2;
       -                                rgl2 = line_index1;
       -                                rgb2 = byte_index1;
       -                        }
       -                        if (text_ret) {
       -                                buffer_copy_text_to_txtbuf(
       -                                    view->buffer, text_ret,
       -                                    rgl1, rgb1,
       -                                    rgl2, rgb2
       -                                );
       -                        }
       -                        ledit_line *line1 = buffer_get_line(view->buffer, rgl1);
       -                        ledit_line *line2 = buffer_get_line(view->buffer, rgl2);
       -                        buffer_delete_line_section_base(view->buffer, rgl1, rgb1, line1->len - rgb1);
       -                        buffer_insert_text_from_line_base(
       -                            view->buffer, rgl1, rgb1, rgl2, rgb2, line2->len - rgb2, NULL
       -                        );
       -                        new_line = rgl1;
       -                        new_byte = rgb1;
       -                        buffer_delete_line_entries_base(view->buffer, rgl1 + 1, rgl2);
       -                }
       -                /* FIXME: too much magic - maybe don't include this here */
       -                if (view->mode == NORMAL)
       -                        new_byte = view_get_legal_normal_pos(view, new_line, new_byte);
       -        }
       -        if (final_range_ret) {
       -                final_range_ret->line1 = rgl1;
       -                final_range_ret->byte1 = rgb1;
       -                final_range_ret->line2 = rgl2;
       -                final_range_ret->byte2 = rgb2;
       +                rgl1 = line_index1;
       +                rgb1 = byte_index1;
       +                rgl2 = line_index2;
       +                rgb2 = byte_index2;
       +                new_line = rgl1;
       +                new_byte = rgb1;
       +                buffer_delete_with_undo_base(
       +                    view->buffer, cur_range,
       +                    start_undo_group, view->mode,
       +                    rgl1, rgb1, rgl2, rgb2, text_ret
       +                );
                }
       +        cur_range.line1 = view->cur_line;
       +        cur_range.byte1 = view->cur_index;
       +        cur_range.line2 = new_line;
       +        cur_range.byte2 = new_byte;
       +        undo_change_last_cur_range(view->buffer->undo, cur_range);
       +        /* FIXME: too much magic - maybe don't include this here */
       +        if (view->mode == NORMAL)
       +                new_byte = view_get_legal_normal_pos(view, new_line, new_byte);
                if (new_line_ret)
                        *new_line_ret = new_line;
                if (new_byte_ret)
       t@@ -2040,27 +1957,11 @@ view_redraw(ledit_view *view) {
                }
        }
        
       -static void
       -undo_insert_helper(void *data, size_t line, size_t byte, char *text, size_t text_len) {
       -        ledit_view *view = (ledit_view *)data;
       -        buffer_insert_text_with_newlines_base(view->buffer, line, byte, text, text_len, NULL, NULL);
       -}
       -
       -static void
       -undo_delete_helper(void *data, size_t line1, size_t byte1, size_t line2, size_t byte2) {
       -        ledit_view *view = (ledit_view *)data;
       -        view_delete_range_base(view, DELETE_CHAR, line1, byte1, line2, byte2, NULL, NULL, NULL, NULL);
       -}
       -
        void
        view_undo(ledit_view *view) {
                /* FIXME: maybe wipe selection */
       -        size_t min_line;
                size_t old_line = view->cur_line;
       -        ledit_undo(
       -            view->buffer->undo, view->mode, view, &undo_insert_helper,
       -            &undo_delete_helper, &view->cur_line, &view->cur_index, &min_line
       -        );
       +        buffer_undo(view->buffer, view->mode, &view->cur_line, &view->cur_index);
                if (view->mode == NORMAL) {
                        view->cur_index = view_get_legal_normal_pos(
                            view, view->cur_line, view->cur_index
       t@@ -2068,23 +1969,13 @@ view_undo(ledit_view *view) {
                }
                view_wipe_line_cursor_attrs(view, old_line);
                view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
       -        /* FIXME: why is this check here? */
       -        if (min_line < view->lines_num) {
       -                buffer_recalc_all_views_from_line(
       -                    view->buffer, min_line > 0 ? min_line - 1 : min_line
       -                );
       -        }
                /* FIXME: show undo message */
        }
        
        void
        view_redo(ledit_view *view) {
       -        size_t min_line;
                size_t old_line = view->cur_line;
       -        ledit_redo(
       -            view->buffer->undo, view->mode, view, &undo_insert_helper,
       -            &undo_delete_helper, &view->cur_line, &view->cur_index, &min_line
       -        );
       +        buffer_redo(view->buffer, view->mode, &view->cur_line, &view->cur_index);
                if (view->mode == NORMAL) {
                        view->cur_index = view_get_legal_normal_pos(
                            view, view->cur_line, view->cur_index
       t@@ -2092,33 +1983,23 @@ view_redo(ledit_view *view) {
                }
                view_wipe_line_cursor_attrs(view, old_line);
                view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
       -        if (min_line < view->lines_num) {
       -                buffer_recalc_all_views_from_line(
       -                    view->buffer, min_line > 0 ? min_line - 1 : min_line
       -                );
       -        }
                /* FIXME: show undo message */
        }
        
        static void
        paste_callback(void *data, char *text, size_t len) {
                ledit_view *view = (ledit_view *)data;
       -        txtbuf ins_buf = {.text = text, .len = len, .cap = len};
       -        ledit_range cur_range, ins_range;
       -        cur_range.line1 = ins_range.line1 = view->cur_line;
       -        cur_range.byte1 = ins_range.byte1 = view->cur_index;
       -        buffer_insert_text_with_newlines(
       -            view->buffer, view->cur_line, view->cur_index,
       -            text, len, &view->cur_line, &view->cur_index
       -        );
       -        cur_range.line2 = ins_range.line2 = view->cur_line;
       -        cur_range.byte2 = ins_range.byte2 = view->cur_index;
       -        undo_push_insert(
       -            view->buffer->undo, &ins_buf, ins_range, cur_range, 1, view->mode
       +        ledit_range cur_range;
       +        cur_range.line1 = view->cur_line;
       +        cur_range.byte1 = view->cur_index;
       +        buffer_insert_with_undo(
       +             view->buffer, cur_range, 1, 1, view->mode,
       +             view->cur_line, view->cur_index, text, len,
       +             &view->cur_line, &view->cur_index
                );
        }
        
       -/* FIXME: guard against buffer being destroyed before paste callback is nulled */
       +/* FIXME: guard against view being destroyed before paste callback is nulled */
        
        void
        view_paste_clipboard(ledit_view *view) {
   DIR diff --git a/view.h b/view.h
       t@@ -339,7 +339,7 @@ size_t view_x_softline_to_pos(ledit_view *view, size_t line, int x, int softline
        size_t view_get_legal_normal_pos(ledit_view *view, size_t line, size_t pos);
        
        /*
       - * Delete a range accorxing to a delete_mode.
       + * Delete a range according to a delete_mode.
         * The line and byte indeces do not need to be sorted (in fact, they often
         * shouldn't be, as shown in the next sentence).
         * If 'delmode' is DELETE_HARDLINE or DELETE_SOFTLINE, 'line_index1' and
       t@@ -352,19 +352,27 @@ size_t view_get_legal_normal_pos(ledit_view *view, size_t line, size_t pos);
         * in the buffer afterwards, although it may have its text deleted.
         * If 'delmode' is DELETE_CHAR, the exact specified range is deleted, and
         * the new line and byte are simply the beginning of the range.
       - * In all cases, the final deletion range is written to 'final_range_ret',
       - * and the deleted text is written to 'text_ret'.
       + * The deleted text is written to 'text_ret'.
         * In normal mode, the new cursor index is always at a valid normal mode
         * position.
         * All return arguments may be NULL.
       + * The deletion is added to the undo stack with 'start_undo_group'
       + * specifying whether a new undo group should be started.
       + * The start cursor position for the undo stack is taken directly from
       + * the view's current position. The end cursor position is the same as
       + * what is returned in 'new_line_ret' and 'new_byte_ret', except that it
       + * is set before calling 'view_get_legal_normal_pos'.
       + * If different cursor positions are needed, call
       + * 'undo_change_last_cur_range' afterwards.
         * This function does not recalculate the line heights or offsets.
         */
        void view_delete_range_base(
       -    ledit_view *view, enum delete_mode delmode,
       +    ledit_view *view,
       +    enum delete_mode delmode, int start_undo_group,
            size_t line_index1, size_t byte_index1,
            size_t line_index2, size_t byte_index2,
            size_t *new_line_ret, size_t *new_byte_ret,
       -    ledit_range *final_range_ret, txtbuf *text_ret
       +    txtbuf *text_ret
        );
        
        /*
       t@@ -372,11 +380,12 @@ void view_delete_range_base(
         * all views are recalculated afterwards.
         */
        void view_delete_range(
       -    ledit_view *view, enum delete_mode delmode,
       +    ledit_view *view,
       +    enum delete_mode delmode, int start_undo_group,
            size_t line_index1, size_t byte_index1,
            size_t line_index2, size_t byte_index2,
            size_t *new_line_ret, size_t *new_byte_ret,
       -    ledit_range *final_range_ret, txtbuf *text_ret
       +    txtbuf *text_ret
        );
        
        /*