URI: 
       tImplement x, X, D, C; fix issue with undo - 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 920ff77cf2e64f74df86d0a415cd6e3180a36c8d
   DIR parent 250e61a764d867385a09548558b0df6c28af8036
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Sun,  7 Nov 2021 21:21:36 +0100
       
       Implement x, X, D, C; fix issue with undo
       
       Diffstat:
         M buffer.c                            |      36 ++++++++++++++++++++-----------
         M buffer.h                            |       4 ++--
         M keys_basic.c                        |     218 ++++++++++++++++++++++++-------
         M keys_basic_config.h                 |      10 ++++++++--
         M undo.c                              |      18 ++++++++++++------
       
       5 files changed, 218 insertions(+), 68 deletions(-)
       ---
   DIR diff --git a/buffer.c b/buffer.c
       t@@ -902,35 +902,45 @@ line_byte_to_char(ledit_line *line, int byte) {
        }
        
        int
       -ledit_buffer_next_cursor_pos(ledit_buffer *buffer, int line, int byte) {
       +ledit_buffer_next_cursor_pos(ledit_buffer *buffer, int line, int byte, int num) {
                int nattrs;
                ledit_line *ll = ledit_buffer_get_line(buffer, line);
                int c = line_byte_to_char(ll, byte);
       -        int cur_byte = ledit_line_next_utf8(ll, byte);
       +        int cur_byte = byte;
                const PangoLogAttr *attrs =
                    pango_layout_get_log_attrs_readonly(ll->layout, &nattrs);
       -        for (int i = c + 1; i < nattrs; i++) {
       -                if (attrs[i].is_cursor_position)
       -                        return cur_byte;
       -                cur_byte = ledit_line_next_utf8(ll, cur_byte);
       +        for (int i = 0; i < num; i++) {
       +                cur_byte = ledit_line_next_utf8(ll, byte);
       +                for (c++; c < nattrs; c++) {
       +                        if (attrs[c].is_cursor_position)
       +                                break;
       +                        cur_byte = ledit_line_next_utf8(ll, cur_byte);
       +                }
       +                if (cur_byte >= ll->len)
       +                        break;
                }
       -        return ll->len;
       +        return cur_byte <= ll->len ? cur_byte : ll->len;
        }
        
        int
       -ledit_buffer_prev_cursor_pos(ledit_buffer *buffer, int line, int byte) {
       +ledit_buffer_prev_cursor_pos(ledit_buffer *buffer, int line, int byte, int num) {
                int nattrs;
                ledit_line *ll = ledit_buffer_get_line(buffer, line);
                int c = line_byte_to_char(ll, byte);
       -        int cur_byte = ledit_line_prev_utf8(ll, byte);
       +        int cur_byte = byte;
                const PangoLogAttr *attrs =
                    pango_layout_get_log_attrs_readonly(ll->layout, &nattrs);
       -        for (int i = c - 1; i >= 0; i--) {
       -                if (attrs[i].is_cursor_position)
       -                        return cur_byte;
       +        for (int i = 0; i < num; i++) {
                        cur_byte = ledit_line_prev_utf8(ll, cur_byte);
       +                for (c--; c >= 0; c--) {
       +                        if (attrs[c].is_cursor_position)
       +                                break;
       +                        cur_byte = ledit_line_prev_utf8(ll, cur_byte);
       +                }
       +                if (cur_byte <= 0)
       +                        break;
                }
       -        return 0;
       +        return cur_byte > 0 ? cur_byte : 0;
        }
        
        static int
   DIR diff --git a/buffer.h b/buffer.h
       t@@ -57,8 +57,8 @@ void ledit_pos_to_x_softline(ledit_line *line, int pos, int *x_ret, int *softlin
        void ledit_x_softline_to_pos(ledit_line *line, int x, int softline, int *pos_ret);
        int ledit_line_next_utf8(ledit_line *line, int index);
        int ledit_line_prev_utf8(ledit_line *line, int index);
       -int ledit_buffer_next_cursor_pos(ledit_buffer *buffer, int line, int byte);
       -int ledit_buffer_prev_cursor_pos(ledit_buffer *buffer, int line, int byte);
       +int ledit_buffer_next_cursor_pos(ledit_buffer *buffer, int line, int byte, int num);
       +int ledit_buffer_prev_cursor_pos(ledit_buffer *buffer, int line, int byte, int num);
        
        void ledit_buffer_next_word(ledit_buffer *buffer, int line, int byte, int num_repeat, int *line_ret, int *byte_ret, int *real_byte_ret);
        void ledit_buffer_next_word_end(ledit_buffer *buffer, int line, int byte, int num_repeat, int *line_ret, int *byte_ret, int *real_byte_ret);
   DIR diff --git a/keys_basic.c b/keys_basic.c
       t@@ -101,6 +101,7 @@ static void unwind_repetition_stack(void);
        static void advance_repetition_stack(void);
        static void discard_repetition_stack(void);
        
       +static int key_stack_empty(void);
        static struct key_stack_elem *push_key_stack(void);
        static struct key_stack_elem *peek_key_stack(void);
        static struct key_stack_elem *pop_key_stack(void);
       t@@ -117,6 +118,7 @@ static void get_new_line_softline(
        static void move_cursor_in_line_dir(ledit_buffer *buffer, int dir, int allow_illegal_index);
        static void move_cursor_logically(ledit_buffer *buffer, int movement_dir, int allow_illegal_index);
        static void change_cb(ledit_buffer *buffer, int line, int char_pos, enum key_type type);
       +static void push_undo_empty_insert(ledit_buffer *buffer, int line, int index, int start_group);
        
        /* FIXME: move to common */
        static void
       t@@ -126,6 +128,11 @@ swap(int *a, int *b) {
                *b = tmp;
        }
        
       +static int
       +key_stack_empty(void) {
       +        return key_stack.len == 0;
       +}
       +
        static struct key_stack_elem *
        push_key_stack(void) {
                struct key_stack_elem *e;
       t@@ -322,14 +329,24 @@ delete_range(
        }
        
        static void
       -insert_text(ledit_buffer *buffer, int line, int index, char *text, int len, int new_line, int new_index, int start_group) {
       +insert_text(
       +    ledit_buffer *buffer,
       +    int line, int index,
       +    char *text, int len,
       +    int cur_line1, int cur_index1,
       +    int cur_line2, int cur_index2, int start_group) {
                if (len < 0)
                        len = strlen(text);
                /* FIXME: this is kind of hacky... */
                txtbuf ins_buf = {.text = text, .len = len, .cap = len};
                ledit_range cur_range, del_range;
       -        cur_range.line1 = buffer->cur_line;
       -        cur_range.byte1 = buffer->cur_index;
       +        if (cur_line1 >= 0 && cur_index1 >= 0) {
       +                cur_range.line1 = cur_line1;
       +                cur_range.byte1 = cur_index1;
       +        } else {
       +                cur_range.line1 = buffer->cur_line;
       +                cur_range.byte1 = buffer->cur_index;
       +        }
                del_range.line1 = line;
                del_range.byte1 = index;
                int cur_line, cur_index;
       t@@ -339,9 +356,9 @@ insert_text(ledit_buffer *buffer, int line, int index, char *text, int len, int 
                );
                /* this is mainly for pasting, where the new line and index
                   should not be at the end of the pasted text */
       -        if (new_line >= 0 && new_index >= 0) {
       -                cur_range.line2 = buffer->cur_line = new_line;
       -                cur_range.byte2 = buffer->cur_index = new_index;
       +        if (cur_line2 >= 0 && cur_index2 >= 0) {
       +                cur_range.line2 = buffer->cur_line = cur_line2;
       +                cur_range.byte2 = buffer->cur_index = cur_index2;
                } else {
                        cur_range.line2 = buffer->cur_line = cur_line;
                        cur_range.byte2 = buffer->cur_index = cur_index;
       t@@ -401,6 +418,73 @@ get_key_repeat(void) {
        }
        
        static struct action
       +delete_chars_forwards(ledit_buffer *buffer, char *text, int len) {
       +        (void)text;
       +        (void)len;
       +        int num = get_key_repeat();
       +        if (num == -1) {
       +                ledit_window_show_message(buffer->window, "Invalid key", -1);
       +                return (struct action){ACTION_NONE, NULL};
       +        } else if (num == 0) {
       +                num = 1;
       +        }
       +        int end_index = ledit_buffer_next_cursor_pos(
       +            buffer, buffer->cur_line, buffer->cur_index, num
       +        );
       +        delete_range(
       +            buffer, 0, 0,
       +            buffer->cur_line, buffer->cur_index,
       +            buffer->cur_line, end_index
       +        );
       +        paste_buffer_line_based = 0;
       +        buffer->cur_index = ledit_buffer_get_legal_normal_pos(
       +            buffer, buffer->cur_line, buffer->cur_index
       +        );
       +        ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
       +        return (struct action){ACTION_NONE, NULL};
       +}
       +
       +static struct action
       +delete_chars_backwards(ledit_buffer *buffer, char *text, int len) {
       +        (void)text;
       +        (void)len;
       +        int num = get_key_repeat();
       +        if (num == -1) {
       +                ledit_window_show_message(buffer->window, "Invalid key", -1);
       +                return (struct action){ACTION_NONE, NULL};
       +        } else if (num == 0) {
       +                num = 1;
       +        }
       +        int start_index = ledit_buffer_prev_cursor_pos(
       +            buffer, buffer->cur_line, buffer->cur_index, num
       +        );
       +        delete_range(
       +            buffer, 0, 0,
       +            buffer->cur_line, start_index,
       +            buffer->cur_line, buffer->cur_index
       +        );
       +        paste_buffer_line_based = 0;
       +        /* I guess this is technically unnecessary since only
       +           text before the current position is deleted */
       +        buffer->cur_index = ledit_buffer_get_legal_normal_pos(
       +            buffer, buffer->cur_line, start_index
       +        );
       +        ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
       +        return (struct action){ACTION_NONE, NULL};
       +}
       +
       +/* used to set cursor - I guess this is sort of a hack */
       +static void
       +push_undo_empty_insert(ledit_buffer *buffer, int line, int index, int start_group) {
       +        txtbuf ins_buf = {.text = "", .len = 0, .cap = 0};
       +        ledit_range ins_range = {.line1 = line, .byte1 = index, .line2 = line, .byte2 = index};
       +        ledit_range cur_range = {.line1 = line, .byte1 = index, .line2 = line, .byte2 = index};
       +        ledit_push_undo_insert(
       +            buffer->undo, &ins_buf, ins_range, cur_range, start_group, buffer->common->mode
       +        );
       +}
       +
       +static struct action
        append_line_above(ledit_buffer *buffer, char *text, int len) {
                int sli, x;
                /* do this here already so the mode group is the same for the newline insertion */
       t@@ -408,10 +492,10 @@ append_line_above(ledit_buffer *buffer, char *text, int len) {
                ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line);
                pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &sli, &x);
                if (sli == 0) {
       -                insert_text(buffer, buffer->cur_line, 0, "\n", -1, buffer->cur_line, 0, 1);
       +                insert_text(buffer, buffer->cur_line, 0, "\n", -1, -1, -1, buffer->cur_line, 0, 1);
                } else {
                        PangoLayoutLine *sl = pango_layout_get_line_readonly(ll->layout, sli);
       -                insert_text(buffer, buffer->cur_line, sl->start_index, "\n\n", -1, buffer->cur_line + 1, 0, 1);
       +                insert_text(buffer, buffer->cur_line, sl->start_index, "\n\n", -1, -1, -1, buffer->cur_line + 1, 0, 1);
                }
                return (struct action){ACTION_NONE, NULL};
        }
       t@@ -424,29 +508,35 @@ append_line_below(ledit_buffer *buffer, char *text, int len) {
                pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &sli, &x);
                PangoLayoutLine *sl = pango_layout_get_line_readonly(ll->layout, sli);
                if (sl->start_index + sl->length == ll->len) {
       -                insert_text(buffer, buffer->cur_line, ll->len, "\n", -1, buffer->cur_line + 1, 0, 1);
       +                insert_text(buffer, buffer->cur_line, ll->len, "\n", -1, -1, -1, buffer->cur_line + 1, 0, 1);
                } else {
       -                insert_text(buffer, buffer->cur_line, sl->start_index + sl->length, "\n\n", -1, buffer->cur_line + 1, 0, 1);
       +                insert_text(buffer, buffer->cur_line, sl->start_index + sl->length, "\n\n", -1, -1, -1, buffer->cur_line + 1, 0, 1);
                }
                return (struct action){ACTION_NONE, NULL};
        }
        
        static struct action
        append_after_cursor(ledit_buffer *buffer, char *text, int len) {
       +        enter_insert(buffer, text, len);
       +        /* make cursor jump back to original position on undo */
       +        push_undo_empty_insert(buffer, buffer->cur_line, buffer->cur_index, 1);
                buffer->cur_index = ledit_buffer_next_cursor_pos(
       -            buffer, buffer->cur_line, buffer->cur_index
       +            buffer, buffer->cur_line, buffer->cur_index, 1
                );
       -        return enter_insert(buffer, text, len);
       +        return (struct action){ACTION_NONE, NULL};
        }
        
        static struct action
        append_after_eol(ledit_buffer *buffer, char *text, int len) {
                int sli, x;
       +        enter_insert(buffer, text, len);
       +        /* make cursor jump back to original position on undo */
       +        push_undo_empty_insert(buffer, buffer->cur_line, buffer->cur_index, 1);
                ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line);
                pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &sli, &x);
                PangoLayoutLine *sl = pango_layout_get_line_readonly(ll->layout, sli);
                buffer->cur_index = sl->start_index + sl->length;
       -        return enter_insert(buffer, text, len);
       +        return (struct action){ACTION_NONE, NULL};
        }
        
        /* FIXME: allow motion callback! */
       t@@ -651,6 +741,57 @@ screen_down(ledit_buffer *buffer, char *text, int len) {
                return (struct action){ACTION_NONE, NULL};
        }
        
       +static struct action
       +delete_to_eol(ledit_buffer *buffer, char *text, int len) {
       +        (void)text;
       +        (void)len;
       +        /* FIXME: move to separate function */
       +        if (!key_stack_empty()) {
       +                clear_key_stack();
       +                ledit_window_show_message(buffer->window, "Invalid key", -1);
       +                return (struct action){ACTION_NONE, NULL};
       +        }
       +        int x, sli;
       +        ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line);
       +        pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &sli, &x);
       +        PangoLayoutLine *sl = pango_layout_get_line_readonly(ll->layout, sli);
       +        delete_range(
       +            buffer, 0, 0,
       +            buffer->cur_line, buffer->cur_index,
       +            buffer->cur_line, sl->start_index + sl->length
       +        );
       +        paste_buffer_line_based = 0;
       +        buffer->cur_index = ledit_buffer_get_legal_normal_pos(
       +            buffer, buffer->cur_line, buffer->cur_index
       +        );
       +        ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
       +        return (struct action){ACTION_NONE, NULL};
       +}
       +
       +static struct action
       +change_to_eol(ledit_buffer *buffer, char *text, int len) {
       +        (void)text;
       +        (void)len;
       +        if (!key_stack_empty()) {
       +                clear_key_stack();
       +                ledit_window_show_message(buffer->window, "Invalid key", -1);
       +                return (struct action){ACTION_NONE, NULL};
       +        }
       +        ledit_buffer_set_mode(buffer, INSERT);
       +        int x, sli;
       +        ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line);
       +        pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &sli, &x);
       +        PangoLayoutLine *sl = pango_layout_get_line_readonly(ll->layout, sli);
       +        delete_range(
       +            buffer, 0, 0,
       +            buffer->cur_line, buffer->cur_index,
       +            buffer->cur_line, sl->start_index + sl->length
       +        );
       +        paste_buffer_line_based = 0;
       +        ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line);
       +        return (struct action){ACTION_NONE, NULL};
       +}
       +
        /* FIXME: clear selection on most commands */
        /* FIXME: don't include escape when repeating change with '.'? */
        static struct action
       t@@ -808,7 +949,7 @@ paste_normal(ledit_buffer *buffer, char *text, int len) {
                        PangoLayoutLine *sl = pango_layout_get_line_readonly(ll->layout, softline);
                        insert_text(
                            buffer, buffer->cur_line, sl->start_index + sl->length,
       -                    "\n", -1, buffer->cur_line, buffer->cur_index, 1
       +                    "\n", -1, -1, -1, buffer->cur_line, buffer->cur_index, 1
                        );
                        int text_len = paste_buffer->len;
                        ll = ledit_buffer_get_line(buffer, buffer->cur_line + 1);
       t@@ -820,19 +961,22 @@ paste_normal(ledit_buffer *buffer, char *text, int len) {
                                /* ensure pasted text is on its own hard line */
                                insert_text(
                                    buffer, buffer->cur_line + 1, 0,
       -                            "\n", -1, buffer->cur_line, buffer->cur_index, 0
       +                            "\n", -1, -1, -1, buffer->cur_line, buffer->cur_index, 0
                                );
                        }
                        insert_text(
                            buffer, buffer->cur_line + 1, 0,
       -                    paste_buffer->text, text_len, buffer->cur_line + 1, 0, 0
       +                    paste_buffer->text, text_len, -1, -1, buffer->cur_line + 1, 0, 0
                        );
                } else {
       +                int old_line = buffer->cur_line;
       +                int old_index = buffer->cur_index;
                        /* must allow illegal index so text can be pasted at end of line */
                        move_cursor_logically(buffer, 1, 1);
                        insert_text(
                            buffer, buffer->cur_line, buffer->cur_index,
       -                    paste_buffer->text, paste_buffer->len, buffer->cur_line, buffer->cur_index, 1
       +                    paste_buffer->text, paste_buffer->len,
       +                    old_line, old_index, buffer->cur_line, buffer->cur_index, 1
                        );
                }
                ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
       t@@ -857,7 +1001,7 @@ paste_normal_backwards(ledit_buffer *buffer, char *text, int len) {
                        PangoLayoutLine *sl = pango_layout_get_line_readonly(ll->layout, softline);
                        insert_text(
                            buffer, buffer->cur_line, sl->start_index,
       -                    "\n", -1, buffer->cur_line, buffer->cur_index, 1
       +                    "\n", -1, -1, -1, buffer->cur_line, buffer->cur_index, 1
                        );
                        int text_len = paste_buffer->len;
                        ll = ledit_buffer_get_line(buffer, buffer->cur_line);
       t@@ -871,18 +1015,19 @@ paste_normal_backwards(ledit_buffer *buffer, char *text, int len) {
                                /* ensure pasted text is on its own hard line */
                                insert_text(
                                    buffer, buffer->cur_line, ll->len,
       -                            "\n", -1, buffer->cur_line, buffer->cur_index, 0
       +                            "\n", -1, -1, -1, buffer->cur_line, buffer->cur_index, 0
                                );
                                new_line = buffer->cur_line + 1;
                        }
                        insert_text(
                            buffer, new_line, 0,
       -                    paste_buffer->text, text_len, new_line, 0, 0
       +                    paste_buffer->text, text_len, -1, -1, new_line, 0, 0
                        );
                } else {
                        insert_text(
                            buffer, buffer->cur_line, buffer->cur_index,
       -                    paste_buffer->text, paste_buffer->len, buffer->cur_line, buffer->cur_index, 1
       +                    paste_buffer->text, paste_buffer->len,
       +                    -1, -1, buffer->cur_line, buffer->cur_index, 1
                        );
                }
                ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
       t@@ -890,24 +1035,6 @@ paste_normal_backwards(ledit_buffer *buffer, char *text, int len) {
                return (struct action){ACTION_NONE, NULL};
        }
        
       -static struct action
       -key_x(ledit_buffer *buffer, char *text, int len) {
       -        (void)buffer;
       -        (void)text;
       -        (void)len;
       -        struct key_stack_elem *e = pop_key_stack();
       -        int num = 1;
       -        clear_key_stack();
       -        if (e && !(e->key & KEY_NUMBER))
       -                return (struct action){ACTION_NONE, NULL};
       -        if (e)
       -                num = e->count;
       -        if (num <= 0)
       -                num = 1;
       -        /* FIXME: actually do something */
       -        return (struct action){ACTION_NONE, NULL};
       -}
       -
        static void
        push_num(int num) {
                struct key_stack_elem *e = peek_key_stack();
       t@@ -1245,7 +1372,7 @@ return_key(ledit_buffer *buffer, char *text, int len) {
                int start_group = 1;
                if (delete_selection(buffer))
                        start_group = 0;
       -        insert_text(buffer, buffer->cur_line, buffer->cur_index, "\n", -1, -1, -1, start_group);
       +        insert_text(buffer, buffer->cur_line, buffer->cur_index, "\n", -1, -1, -1, -1, -1, start_group);
                /* FIXME: these aren't needed, right? This only works in insert mode
                 * anyways, so there's nothing to wipe */
                /* ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line);
       t@@ -1571,7 +1698,7 @@ redo(ledit_buffer *buffer, char *text, int len) {
        static struct action
        insert_mode_insert_text(ledit_buffer *buffer, char *text, int len) {
                delete_selection(buffer);
       -        insert_text(buffer, buffer->cur_line, buffer->cur_index, text, len, -1, -1, 1);
       +        insert_text(buffer, buffer->cur_line, buffer->cur_index, text, len, -1, -1, -1, -1, 1);
                return (struct action){ACTION_NONE, NULL};
        }
        
       t@@ -1642,9 +1769,10 @@ search_str_forwards(char *haystack, int hlen, char *needle, int nlen, int start_
        /* just to make the macro below works for all cases */
        /* FIXME: is there a more elegant way to do this? */
        static int
       -dummy_cursor_helper(ledit_buffer *buffer, int line, int index) {
       +dummy_cursor_helper(ledit_buffer *buffer, int line, int index, int num) {
                (void)buffer;
                (void)line;
       +        (void)num;
                return index;
        }
        
       t@@ -1674,13 +1802,13 @@ name##_cb(ledit_buffer *buffer, char *text, int len) {                          
                if (new_index >= 0) {                                                      \
                        if (cb != NULL) {                                                  \
                                new_index = cur_funcm(                                     \
       -                            buffer, buffer->cur_line, new_index                    \
       +                            buffer, buffer->cur_line, new_index, 1                 \
                                );                                                         \
                                cb(buffer, buffer->cur_line, new_index, KEY_MOTION_CHAR);  \
                        } else {                                                           \
                                if (buffer->common->mode == VISUAL) {                      \
                                        buffer->cur_index = cur_funcv(                     \
       -                                    buffer, buffer->cur_line, new_index            \
       +                                    buffer, buffer->cur_line, new_index, 1         \
                                        );                                                 \
                                        ledit_buffer_set_selection(                        \
                                            buffer,                                        \
       t@@ -1689,7 +1817,7 @@ name##_cb(ledit_buffer *buffer, char *text, int len) {                          
                                        );                                                 \
                                } else {                                                   \
                                        buffer->cur_index = cur_funcn(                     \
       -                                    buffer, buffer->cur_line, new_index            \
       +                                    buffer, buffer->cur_line, new_index, 1         \
                                        );                                                 \
                                        ledit_buffer_set_line_cursor_attrs(                \
                                            buffer, buffer->cur_line, buffer->cur_index    \
   DIR diff --git a/keys_basic_config.h b/keys_basic_config.h
       t@@ -41,7 +41,6 @@ static struct action push_6(ledit_buffer *buffer, char *text, int len);
        static struct action push_7(ledit_buffer *buffer, char *text, int len);
        static struct action push_8(ledit_buffer *buffer, char *text, int len);
        static struct action push_9(ledit_buffer *buffer, char *text, int len);
       -static struct action key_x(ledit_buffer *buffer, char *text, int len);
        static struct action key_d(ledit_buffer *buffer, char *text, int len);
        static struct action enter_visual(ledit_buffer *buffer, char *text, int len);
        static struct action switch_selection_end(ledit_buffer *buffer, char *text, int len);
       t@@ -84,6 +83,10 @@ static struct action find_next_char_forwards(ledit_buffer *buffer, char *text, i
        static struct action find_next_char_backwards(ledit_buffer *buffer, char *text, int len);
        static struct action find_char_forwards(ledit_buffer *buffer, char *text, int len);
        static struct action find_char_backwards(ledit_buffer *buffer, char *text, int len);
       +static struct action change_to_eol(ledit_buffer *buffer, char *text, int len);
       +static struct action delete_to_eol(ledit_buffer *buffer, char *text, int len);
       +static struct action delete_chars_forwards(ledit_buffer *buffer, char *text, int len);
       +static struct action delete_chars_backwards(ledit_buffer *buffer, char *text, int len);
        
        /* FIXME: maybe sort these and use binary search
           -> but that would mess with the catch-all keys */
       t@@ -117,7 +120,8 @@ static struct key keys_en[] = {
                {"7",  0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_7},
                {"8",  0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_8},
                {"9",  0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_9},
       -        {"x",  0, 0, NORMAL, KEY_ANY, KEY_NUMBERALLOWED, &key_x},
       +        {"x",  0, 0, NORMAL, KEY_ANY, KEY_NUMBERALLOWED, &delete_chars_forwards},
       +        {"X",  0, 0, NORMAL, KEY_ANY, KEY_NUMBERALLOWED, &delete_chars_backwards},
                {"d",  0, 0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION|KEY_NUMBERALLOWED, &key_d},
                {"c",  0, 0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION|KEY_NUMBERALLOWED, &change},
                {"v",  0, 0, NORMAL, KEY_ANY, KEY_ANY, &enter_visual},
       t@@ -157,6 +161,8 @@ static struct key keys_en[] = {
                {"o",  0, 0, NORMAL, KEY_ANY, KEY_ANY, &append_line_below},
                {"m",  0, 0, NORMAL, KEY_ANY, KEY_ANY, &mark_line},
                {"'",  0, 0, NORMAL, KEY_ANY, KEY_ANY, &jump_to_mark},
       +        {"C",  0, 0, NORMAL, KEY_ANY, KEY_ANY, &change_to_eol},
       +        {"D",  0, 0, NORMAL, KEY_ANY, KEY_ANY, &delete_to_eol},
                {"t",  0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBERALLOWED, &find_next_char_forwards},
                {"T",  0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBERALLOWED, &find_next_char_backwards},
                {"f",  0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBERALLOWED, &find_char_forwards},
   DIR diff --git a/undo.c b/undo.c
       t@@ -84,8 +84,8 @@ push_undo(
            ledit_undo_stack *undo, txtbuf *text,
            ledit_range insert_range,
            ledit_range cursor_range,
       -    int start_group, enum operation type,
       -    enum ledit_mode mode) {
       +    int start_group,
       +    enum operation type, enum ledit_mode mode) {
                undo_elem *old = peek_undo_elem(undo);
                int last_group = old == NULL ? 0 : old->group;
                int last_mode_group = old == NULL ? 0 : old->mode_group;
       t@@ -106,8 +106,7 @@ push_undo(
        void
        ledit_push_undo_insert(
            ledit_undo_stack *undo, txtbuf *text,
       -    ledit_range insert_range,
       -    ledit_range cursor_range,
       +    ledit_range insert_range, ledit_range cursor_range,
            int start_group, enum ledit_mode mode) {
                push_undo(
                    undo, text, insert_range, cursor_range,
       t@@ -118,8 +117,7 @@ ledit_push_undo_insert(
        void
        ledit_push_undo_delete(
            ledit_undo_stack *undo, txtbuf *text,
       -    ledit_range insert_range,
       -    ledit_range cursor_range,
       +    ledit_range insert_range, ledit_range cursor_range,
            int start_group, enum ledit_mode mode) {
                push_undo(
                    undo, text, insert_range, cursor_range,
       t@@ -132,6 +130,10 @@ ledit_undo(ledit_undo_stack *undo, enum ledit_mode mode, void *callback_data,
            undo_insert_callback insert_cb, undo_delete_callback delete_cb,
            int *cur_line_ret, int *cur_index_ret, int *min_line_ret) {
                undo_elem *e;
       +        /* skip empty elements */
       +        while (undo->cur >= 0 && undo->stack[undo->cur].text->len == 0) {
       +                undo->cur--;
       +        }
                if (undo->cur < 0)
                        return UNDO_OLDEST_CHANGE;
                int group = undo->stack[undo->cur].group;
       t@@ -188,6 +190,10 @@ ledit_redo(ledit_undo_stack *undo, enum ledit_mode mode, void *callback_data,
            undo_insert_callback insert_cb, undo_delete_callback delete_cb,
            int *cur_line_ret, int *cur_index_ret, int *min_line_ret) {
                undo_elem *e;
       +        /* skip elements where no text is changed */
       +        while (undo->cur < undo->len - 1 && undo->stack[undo->cur + 1].text->len == 0) {
       +                undo->cur++;
       +        }
                if (undo->cur >= undo->len - 1)
                        return UNDO_NEWEST_CHANGE;
                undo->cur++;