URI: 
       tAdd basic (buggy) support for command repetition - 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 bc282da2c2bfd820f810d1e03bc829fe3928f3e5
   DIR parent 9df066a3d594aeeca41677744f0f29a81901f124
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Sun, 24 Oct 2021 13:48:53 +0200
       
       Add basic (buggy) support for command repetition
       
       Diffstat:
         M keys_basic.c                        |     186 ++++++++++++++++++++++++++-----
         M keys_basic_config.h                 |       2 ++
         M memory.c                            |       8 ++++++++
         M memory.h                            |       1 +
       
       4 files changed, 171 insertions(+), 26 deletions(-)
       ---
   DIR diff --git a/keys_basic.c b/keys_basic.c
       t@@ -28,6 +28,22 @@
        /* this is supposed to be global for all buffers */
        txtbuf *paste_buffer = NULL;
        
       +struct repetition_stack_elem {
       +        char *key_text;
       +        int len;
       +        KeySym sym;
       +        unsigned int key_state;
       +        int lang_index;
       +};
       +
       +static struct {
       +        int replaying;
       +        size_t len, alloc, cur_pos;
       +        struct repetition_stack_elem *stack;
       +        size_t tmp_len, tmp_alloc;
       +        struct repetition_stack_elem *tmp_stack;
       +} repetition_stack = {0, 0, 0, 0, NULL, 0, 0, NULL};
       +
        struct key_stack_elem {
                enum key_type key;
                enum key_type followup; /* allowed keys to complete the keybinding */
       t@@ -46,6 +62,14 @@ static struct {
                struct key_stack_elem *stack;
        } key_stack = {0, 0, NULL};
        
       +/* No, this isn't actually a stack. So what? */
       +static struct repetition_stack_elem *push_repetition_stack(void);
       +static void finalize_repetition_stack(void);
       +static struct repetition_stack_elem *get_cur_repetition_stack_elem(void);
       +static void unwind_repetition_stack(void);
       +static void advance_repetition_stack(void);
       +static void discard_repetition_stack(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@@ -86,7 +110,7 @@ push_key_stack(void) {
                e->data1 = 0;
                e->data2 = 0;
                key_stack.len++;
       -        return &key_stack.stack[key_stack.len - 1];
       +        return e;
        }
        
        /* Note: for peek and pop, the returned element is only valid
       t@@ -113,6 +137,67 @@ clear_key_stack(void) {
                key_stack.len = 0;
        }
        
       +static struct repetition_stack_elem *
       +push_repetition_stack(void) {
       +        struct repetition_stack_elem *e;
       +        if (repetition_stack.tmp_len >= repetition_stack.tmp_alloc) {
       +                size_t new_alloc = repetition_stack.tmp_alloc > 0 ? repetition_stack.tmp_alloc * 2 : 4;
       +                repetition_stack.tmp_stack = ledit_realloc(
       +                    repetition_stack.tmp_stack,
       +                    new_alloc * sizeof(struct repetition_stack_elem)
       +                );
       +                repetition_stack.tmp_alloc = new_alloc;
       +        }
       +        e = &repetition_stack.tmp_stack[repetition_stack.tmp_len];
       +        e->key_text = NULL;
       +        e->len = 0;
       +        e->sym = 0;
       +        e->key_state = 0;
       +        e->lang_index = 0;
       +        repetition_stack.tmp_len++;
       +        return e;
       +}
       +
       +static struct repetition_stack_elem *
       +get_cur_repetition_stack_elem(void) {
       +        if (repetition_stack.cur_pos >= repetition_stack.len)
       +                return NULL;
       +        return &repetition_stack.stack[repetition_stack.cur_pos];
       +}
       +
       +static void
       +unwind_repetition_stack(void) {
       +        repetition_stack.cur_pos = 0;
       +}
       +
       +static void
       +discard_repetition_stack(void) {
       +        if (repetition_stack.replaying)
       +                return;
       +        repetition_stack.tmp_len = 0;
       +}
       +
       +static void
       +advance_repetition_stack(void) {
       +        repetition_stack.cur_pos++;
       +}
       +
       +static void
       +finalize_repetition_stack(void) {
       +        if (repetition_stack.replaying)
       +                return;
       +        size_t tmp;
       +        struct repetition_stack_elem *tmpstack;
       +        repetition_stack.len = repetition_stack.tmp_len;
       +        repetition_stack.tmp_len = 0;
       +        tmp = repetition_stack.alloc;
       +        repetition_stack.alloc = repetition_stack.tmp_alloc;
       +        repetition_stack.tmp_alloc = tmp;
       +        tmpstack = repetition_stack.stack;
       +        repetition_stack.stack = repetition_stack.tmp_stack;
       +        repetition_stack.tmp_stack = tmpstack;
       +}
       +
        /* get the new line and softline when moving 'movement' softlines up or
           down (negative means up, positive means down) */
        static void
       t@@ -295,6 +380,7 @@ key_d_cb(ledit_buffer *buffer, int line, int char_pos, enum key_type type) {
                    line, char_pos
                );
                ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
       +        finalize_repetition_stack();
        }
        
        static struct action
       t@@ -523,6 +609,7 @@ move_cursor_left_right(ledit_buffer *buffer, int dir) {
                        } else if (buffer->common->mode == NORMAL) {
                                ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
                        }
       +                discard_repetition_stack();
                }
                clear_key_stack();
        }
       t@@ -621,6 +708,8 @@ escape_key(ledit_buffer *buffer, char *text, int len) {
                (void)text;
                (void)len;
                clear_key_stack(); /* just in case... */
       +        if (buffer->common->mode == INSERT)
       +                finalize_repetition_stack();
                if (buffer->common->mode == INSERT &&
                    (buffer->sel.line1 != buffer->sel.line2 ||
                     buffer->sel.byte1 != buffer->sel.byte2)) {
       t@@ -708,6 +797,7 @@ move_cursor_up_down(ledit_buffer *buffer, int dir) {
                        } else if (buffer->common->mode == NORMAL) {
                                ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
                        }
       +                discard_repetition_stack();
                }
                clear_key_stack();
        }
       t@@ -749,6 +839,7 @@ cursor_to_beginning(ledit_buffer *buffer, char *text, int len) {
                                    buffer, buffer->cur_line, buffer->cur_index
                                );
                        }
       +                discard_repetition_stack();
                }
                clear_key_stack();
                return (struct action){ACTION_NONE, NULL};
       t@@ -788,6 +879,7 @@ enter_commandedit(ledit_buffer *buffer, char *text, int len) {
                ledit_window_set_bottom_bar_cursor(buffer->window, 1);
                ledit_command_set_type(CMD_EDIT);
                ledit_window_set_bottom_bar_text_shown(buffer->window, 1);
       +        discard_repetition_stack();
                return (struct action){ACTION_GRABKEY, &ledit_command_key_handler};
        }
        
       t@@ -799,6 +891,7 @@ enter_searchedit_forward(ledit_buffer *buffer, char *text, int len) {
                ledit_window_set_bottom_bar_cursor(buffer->window, 1);
                ledit_command_set_type(CMD_EDITSEARCH);
                ledit_window_set_bottom_bar_text_shown(buffer->window, 1);
       +        discard_repetition_stack();
                return (struct action){ACTION_GRABKEY, &ledit_command_key_handler};
        }
        
       t@@ -810,6 +903,7 @@ enter_searchedit_backward(ledit_buffer *buffer, char *text, int len) {
                ledit_window_set_bottom_bar_cursor(buffer->window, 1);
                ledit_command_set_type(CMD_EDITSEARCHB);
                ledit_window_set_bottom_bar_text_shown(buffer->window, 1);
       +        discard_repetition_stack();
                return (struct action){ACTION_GRABKEY, &ledit_command_key_handler};
        }
        
       t@@ -819,6 +913,7 @@ key_search_next(ledit_buffer *buffer, char *text, int len) {
                (void)text;
                (void)len;
                search_next(buffer);
       +        discard_repetition_stack();
                return (struct action){ACTION_NONE, NULL};
        }
        
       t@@ -827,6 +922,7 @@ key_search_prev(ledit_buffer *buffer, char *text, int len) {
                (void)text;
                (void)len;
                search_prev(buffer);
       +        discard_repetition_stack();
                return (struct action){ACTION_NONE, NULL};
        }
        
       t@@ -838,6 +934,7 @@ show_line(ledit_buffer *buffer, char *text, int len) {
                char *str = ledit_malloc(textlen + 1);
                snprintf(str, textlen + 1, "Line %d of %d", buffer->cur_line + 1, buffer->lines_num);
                ledit_window_show_message(buffer->window, str, textlen);
       +        discard_repetition_stack();
                return (struct action){ACTION_NONE, NULL};
        }
        
       t@@ -850,6 +947,7 @@ undo(ledit_buffer *buffer, char *text, int len) {
                ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line);
                ledit_buffer_undo(buffer);
                ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
       +        finalize_repetition_stack();
                return (struct action){ACTION_NONE, NULL};
        }
        
       t@@ -861,6 +959,7 @@ redo(ledit_buffer *buffer, char *text, int len) {
                ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line);
                ledit_buffer_redo(buffer);
                ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
       +        finalize_repetition_stack();
                return (struct action){ACTION_NONE, NULL};
        }
        
       t@@ -877,6 +976,7 @@ clipcopy(ledit_buffer *buffer, char *text, int len) {
                (void)len;
                /* FIXME: abstract this through buffer */
                clipboard_primary_to_clipboard(buffer->window);
       +        discard_repetition_stack();
                return (struct action){ACTION_NONE, NULL};
        }
        
       t@@ -885,53 +985,87 @@ clippaste(ledit_buffer *buffer, char *text, int len) {
                (void)text;
                (void)len;
                ledit_buffer_paste_clipboard(buffer);
       +        finalize_repetition_stack();
                return (struct action){ACTION_NONE, NULL};
        }
        
       -struct action
       -basic_key_handler(ledit_buffer *buffer, XEvent *event, int lang_index) {
       -        char buf[64];
       -        KeySym sym;
       -        int n;
       -
       +static struct action
       +handle_key(ledit_buffer *buffer, char *key_text, int len, KeySym sym, unsigned int key_state, int lang_index, int *found) {
       +        struct action act = {ACTION_NONE, NULL};
                struct key *cur_keys = keys[lang_index].keys;
                int num_keys = keys[lang_index].num_keys;
       -        unsigned int key_state = event->xkey.state;
       -        preprocess_key(buffer->window, event, &sym, buf, sizeof(buf), &n);
       -
       -        int found = 0;
                struct key_stack_elem *e = peek_key_stack();
       -        /* FIXME: only hide when actually necessary */
       -        ledit_window_hide_message(buffer->window);
       -        struct action act;
       +        *found = 0;
                for (int i = 0; i < num_keys; i++) {
                        if (cur_keys[i].text) {
       -                        if (n > 0 &&
       +                        if (len > 0 &&
                                    (cur_keys[i].modes & buffer->common->mode) &&
                                    (!e || (e->key & cur_keys[i].prev_keys)) &&
       -                             ((!strncmp(cur_keys[i].text, buf, n) &&
       +                             ((!strncmp(cur_keys[i].text, key_text, len) &&
                                       match_key(cur_keys[i].mods, key_state & ~ShiftMask)) ||
                                      cur_keys[i].text[0] == '\0')) {
                                        /* FIXME: seems a bit hacky to remove shift, but it
                                           is needed to make keys that use shift match */
       -                                act = cur_keys[i].func(buffer, buf, n);
       -                                found = 1;
       +                                act = cur_keys[i].func(buffer, key_text, len);
       +                                *found = 1;
                                        break;
                                }
                        } else if ((cur_keys[i].modes & buffer->common->mode) &&
                                    cur_keys[i].keysym == sym &&
                                    match_key(cur_keys[i].mods, key_state)) {
       -                        act = cur_keys[i].func(buffer, buf, n);
       -                        found = 1;
       +                        act = cur_keys[i].func(buffer, key_text, len);
       +                        *found = 1;
                                break;
                        }
                }
       +        return act;
       +}
       +
       +static struct action
       +repeat_command(ledit_buffer *buffer, char *text, int len) {
       +        (void)buffer;
       +        (void)text;
       +        (void)len;
       +        int found;
       +        repetition_stack.replaying = 1;
       +        clear_key_stack();
       +        unwind_repetition_stack();
       +        struct repetition_stack_elem *e = get_cur_repetition_stack_elem();
       +        while (e) {
       +                (void)handle_key(buffer, e->key_text, e->len, e->sym, e->key_state, e->lang_index, &found);
       +                advance_repetition_stack();
       +                e = get_cur_repetition_stack_elem();
       +        }
       +        repetition_stack.replaying = 0;
       +        discard_repetition_stack();
       +        clear_key_stack();
       +        return (struct action){ACTION_NONE, NULL};
       +}
       +
       +struct action
       +basic_key_handler(ledit_buffer *buffer, XEvent *event, int lang_index) {
       +        char buf[64];
       +        KeySym sym;
       +        int n;
       +
       +        unsigned int key_state = event->xkey.state;
       +        preprocess_key(buffer->window, event, &sym, buf, sizeof(buf), &n);
       +
       +        struct repetition_stack_elem *re = push_repetition_stack();
       +        re->key_text = ledit_strndup(buf, (size_t)n);
       +        re->len = n;
       +        re->sym = sym;
       +        re->key_state = key_state;
       +        re->lang_index = lang_index;
       +
       +        /* FIXME: only hide when actually necessary */
       +        ledit_window_hide_message(buffer->window);
       +        int found = 0;
       +        struct action act = handle_key(buffer, buf, n, sym, key_state, lang_index, &found);
       +
                /* FIXME: only do this when necessary */
       -        if (found) {
       +        if (found)
                        ledit_buffer_ensure_cursor_shown(buffer);
       -                return act;
       -        } else {
       -                /* FIXME: maybe show error */
       -                return (struct action){ACTION_NONE, NULL};
       -        }
       +        /* FIXME: maybe show error if not found */
       +        return act;
        }
   DIR diff --git a/keys_basic_config.h b/keys_basic_config.h
       t@@ -56,6 +56,7 @@ static struct action key_search_prev(ledit_buffer *buffer, char *text, int len);
        static struct action undo(ledit_buffer *buffer, char *text, int len);
        static struct action redo(ledit_buffer *buffer, char *text, int len);
        static struct action insert_mode_insert_text(ledit_buffer *buffer, char *text, int len);
       +static struct action repeat_command(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@@ -98,6 +99,7 @@ static struct key keys_en[] = {
                {"N",  0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &key_search_prev},
                {"u",  0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &undo},
                {"U",  0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &redo},
       +        {".",  0, 0, NORMAL, KEY_ANY, KEY_ANY, &repeat_command}, /* FIXME: only allow after finished key sequence */
                {"z",  ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &undo},
                {"y",  ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &redo},
                {"", 0, 0, INSERT, KEY_ANY, KEY_ANY, &insert_mode_insert_text}
   DIR diff --git a/memory.c b/memory.c
       t@@ -16,6 +16,14 @@ ledit_strdup(const char *s) {
                return str;
        }
        
       +char *
       +ledit_strndup(const char *s, size_t n) {
       +        char *str = strndup(s, n);
       +        if (!str)
       +                fatal_err("Out of memory.\n");
       +        return str;
       +}
       +
        void *
        ledit_malloc(size_t size) {
                void *ptr = malloc(size);
   DIR diff --git a/memory.h b/memory.h
       t@@ -1,4 +1,5 @@
        char *ledit_strdup(const char *s);
       +char *ledit_strndup(const char *s, size_t n);
        void *ledit_malloc(size_t size);
        void *ledit_calloc(size_t nmemb, size_t size);
        void *ledit_realloc(void *ptr, size_t size);