URI: 
       tAdd commands for jumping to character - 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 250e61a764d867385a09548558b0df6c28af8036
   DIR parent 3d0707ecc637d39e0f853d036c592bbcbe7675f7
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Sat,  6 Nov 2021 23:42:41 +0100
       
       Add commands for jumping to character
       
       Diffstat:
         M buffer.c                            |      16 ++++++++++++++++
         M buffer.h                            |       1 +
         M keys_basic.c                        |     161 +++++++++++++++++++++++++++++--
         M keys_basic_config.h                 |       8 ++++++++
         M keys_command_config.h               |       2 --
       
       5 files changed, 179 insertions(+), 9 deletions(-)
       ---
   DIR diff --git a/buffer.c b/buffer.c
       t@@ -917,6 +917,22 @@ ledit_buffer_next_cursor_pos(ledit_buffer *buffer, int line, int byte) {
                return ll->len;
        }
        
       +int
       +ledit_buffer_prev_cursor_pos(ledit_buffer *buffer, int line, int byte) {
       +        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);
       +        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;
       +                cur_byte = ledit_line_prev_utf8(ll, cur_byte);
       +        }
       +        return 0;
       +}
       +
        static int
        line_next_word(ledit_line *line, int byte, int char_index, int wrapped_line, int *char_ret, int *real_byte_ret) {
                int c, nattrs;
   DIR diff --git a/buffer.h b/buffer.h
       t@@ -58,6 +58,7 @@ 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);
        
        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@@ -56,6 +56,8 @@ static struct {
                struct repetition_stack_elem *tmp_stack;
        } repetition_stack = {0, 0, 0, 0, NULL, 0, 0, NULL};
        
       +typedef void (*motion_callback)(ledit_buffer *buffer, int line, int char_pos, enum key_type type);
       +
        struct key_stack_elem {
                enum key_type key;
                enum key_type followup; /* allowed keys to complete the keybinding */
       t@@ -63,7 +65,7 @@ struct key_stack_elem {
                 * line and char_pos already include the repetition stored in this stack
                 * element; type is the type of motion command (used to determine if
                 * the command should operate on lines or chars) */
       -        void (*motion_cb)(ledit_buffer *buffer, int line, int char_pos, enum key_type type);
       +        motion_callback motion_cb;
                int count; /* number of repetitions */
                int data1; /* misc. data 1 */
                int data2; /* misc. data 2 */
       t@@ -74,6 +76,8 @@ static struct {
                struct key_stack_elem *stack;
        } key_stack = {0, 0, NULL};
        
       +static struct action (*grab_char_cb)(ledit_buffer *buffer, char *text, int len) = NULL;
       +
        void
        basic_key_cleanup(void) {
                /* this should be safe since push_repetition_stack sets all new
       t@@ -1590,12 +1594,157 @@ clippaste(ledit_buffer *buffer, char *text, int len) {
                return (struct action){ACTION_NONE, NULL};
        }
        
       +static int
       +get_key_repeat_and_motion_cb(motion_callback *cb_ret) {
       +        int num = 1;
       +        struct key_stack_elem *e = pop_key_stack();
       +        if (e != NULL) {
       +                if (e->key & KEY_NUMBER) {
       +                        num = e->count > 0 ? e->count : 1;
       +                        e = pop_key_stack();
       +                }
       +                if (e != NULL)
       +                        num *= (e->count > 0 ? e->count : 1);
       +        }
       +        if (cb_ret != NULL && e != NULL && e->motion_cb != NULL)
       +                *cb_ret = e->motion_cb;
       +        return num;
       +}
       +
       +/* FIXME: make sure the found position is valid cursor position? */
       +static int
       +search_str_backwards(char *haystack, int hlen, char *needle, int nlen, int start_index) {
       +        if (start_index < 0 || start_index > hlen)
       +                return -1;
       +        int new_index = start_index;
       +        for (new_index--; new_index >= 0; new_index--) {
       +                if (!strncmp(haystack + new_index, needle, nlen))
       +                        return new_index;
       +        }
       +        return -1;
       +}
       +
       +static int
       +search_str_forwards(char *haystack, int hlen, char *needle, int nlen, int start_index) {
       +        if (start_index < 0 || start_index >= hlen)
       +                return -1;
       +        /* duplicate so it is nul-terminated */
       +        char *search_str = ledit_strndup(needle, nlen);
       +        char *res = strstr(haystack + start_index + 1, search_str);
       +        free(search_str);
       +        /* FIXME: is this legal? */
       +        if (res)
       +                return (int)(res - haystack);
       +        else
       +                return -1;
       +}
       +
       +/* 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) {
       +        (void)buffer;
       +        (void)line;
       +        return index;
       +}
       +
       +/* FIXME: call normalize on line first? */
       +/* FIXME: add checks to functions that current mode is supported */
       +
       +/* name is the name of the generated pair of functions
       +   search_func is used to get the next index (possibly called
       +   repeatedly if there is a repeat number on the key stack)
       +   funcm = func motion, funcn = func normal, funcv = func visual
       +   -> these are called to modify the index returned by search_func
       +      cur_funcm is called to get the index for a motion callback
       +      cur_funcn is called to position the cursor in normal mode
       +      cur_funcv is called to position the cursor in visual mode */
       +#define GEN_MOVE_TO_CHAR(name, search_func, cur_funcm, cur_funcn, cur_funcv)       \
       +static struct action                                                               \
       +name##_cb(ledit_buffer *buffer, char *text, int len) {                             \
       +        motion_callback cb = NULL;                                                 \
       +        int num = get_key_repeat_and_motion_cb(&cb);                               \
       +        ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line);          \
       +        int new_index = buffer->cur_index;                                         \
       +        for (int i = 0; i < num; i++) {                                            \
       +                new_index = search_func(ll->text, ll->len, text, len, new_index);  \
       +                if (new_index == -1)                                               \
       +                        break;                                                     \
       +        }                                                                          \
       +        if (new_index >= 0) {                                                      \
       +                if (cb != NULL) {                                                  \
       +                        new_index = cur_funcm(                                     \
       +                            buffer, buffer->cur_line, new_index                    \
       +                        );                                                         \
       +                        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            \
       +                                );                                                 \
       +                                ledit_buffer_set_selection(                        \
       +                                    buffer,                                        \
       +                                    buffer->sel.line1, buffer->sel.byte1,          \
       +                                    buffer->cur_line, buffer->cur_index            \
       +                                );                                                 \
       +                        } else {                                                   \
       +                                buffer->cur_index = cur_funcn(                     \
       +                                    buffer, buffer->cur_line, new_index            \
       +                                );                                                 \
       +                                ledit_buffer_set_line_cursor_attrs(                \
       +                                    buffer, buffer->cur_line, buffer->cur_index    \
       +                                );                                                 \
       +                        }                                                          \
       +                        discard_repetition_stack();                                \
       +                }                                                                  \
       +        }                                                                          \
       +        clear_key_stack();                                                         \
       +        grab_char_cb = NULL;                                                       \
       +        return (struct action){ACTION_NONE, NULL};                                 \
       +}                                                                                  \
       +                                                                                   \
       +static struct action                                                               \
       +name(ledit_buffer *buffer, char *text, int len) {                                  \
       +        (void)buffer;                                                              \
       +        (void)text;                                                                \
       +        (void)len;                                                                 \
       +        grab_char_cb = &name##_cb;                                                 \
       +        return (struct action){ACTION_NONE, NULL};                                 \
       +}
       +
       +/* FIXME: more sensible names */
       +/* FIXME: dummy_cursor_helper is kind of ugly */
       +GEN_MOVE_TO_CHAR(
       +    find_next_char_forwards, search_str_forwards,
       +    dummy_cursor_helper, ledit_buffer_prev_cursor_pos, dummy_cursor_helper
       +)
       +GEN_MOVE_TO_CHAR(
       +    find_next_char_backwards, search_str_backwards,
       +    ledit_buffer_next_cursor_pos, ledit_buffer_next_cursor_pos, ledit_buffer_next_cursor_pos
       +)
       +GEN_MOVE_TO_CHAR(
       +    find_char_forwards, search_str_forwards,
       +    ledit_buffer_next_cursor_pos, dummy_cursor_helper, dummy_cursor_helper
       +)
       +GEN_MOVE_TO_CHAR(
       +    find_char_backwards, search_str_backwards,
       +    dummy_cursor_helper, dummy_cursor_helper, dummy_cursor_helper
       +)
       +
        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;
                struct key_stack_elem *e = peek_key_stack();
       +        /* FIXME: allow to escape this grabbing somehow */
       +        /* -> I guess escape *does* actually work because it
       +           results in ascii 1b (escape) in this string, which
       +           will usually not be found in the text (but this is
       +           a bit of a hack) */
       +        if (len > 0 && grab_char_cb) {
       +                *found = 1;
       +                return grab_char_cb(buffer, key_text, len);
       +        }
                *found = 0;
                for (int i = 0; i < num_keys; i++) {
                        if (cur_keys[i].text) {
       t@@ -1607,19 +1756,17 @@ handle_key(ledit_buffer *buffer, char *key_text, int len, KeySym sym, unsigned i
                                      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, key_text, len);
                                        *found = 1;
       -                                break;
       +                                return cur_keys[i].func(buffer, key_text, len);
                                }
                        } 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, key_text, len);
                                *found = 1;
       -                        break;
       +                        return cur_keys[i].func(buffer, key_text, len);
                        }
                }
       -        return act;
       +        return (struct action){ACTION_NONE, NULL};
        }
        
        static struct action
   DIR diff --git a/keys_basic_config.h b/keys_basic_config.h
       t@@ -80,6 +80,10 @@ static struct action append_after_eol(ledit_buffer *buffer, char *text, int len)
        static struct action append_after_cursor(ledit_buffer *buffer, char *text, int len);
        static struct action append_line_above(ledit_buffer *buffer, char *text, int len);
        static struct action append_line_below(ledit_buffer *buffer, char *text, int len);
       +static struct action find_next_char_forwards(ledit_buffer *buffer, char *text, int len);
       +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);
        
        /* FIXME: maybe sort these and use binary search
           -> but that would mess with the catch-all keys */
       t@@ -153,6 +157,10 @@ 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},
       +        {"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},
       +        {"F",  0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBERALLOWED, &find_char_backwards},
                {"", 0, 0, INSERT, KEY_ANY, KEY_ANY, &insert_mode_insert_text}
        };
        
   DIR diff --git a/keys_command_config.h b/keys_command_config.h
       t@@ -29,8 +29,6 @@ static struct key keys_en[] = {
                {"", 0, 0, CMD_EDIT, &edit_insert_text},
                {"", 0, 0, CMD_EDITSEARCH, &edit_insert_text},
                {"", 0, 0, CMD_EDITSEARCHB, &edit_insert_text},
       -        /* FIXME: also allow non-text keys for marks?
       -           OpenBSD nvi seems to allow it, at least sort of. */
                {"", 0, 0, CMD_MARKLINE, &mark_line},
                {"", 0, 0, CMD_JUMPTOMARK, &jump_to_mark}
        };