URI: 
       tImplement J, I, and ^ - 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 61c47698d180e2e9e81df98c5ac9d7304594a95b
   DIR parent c5fa1b6794a0e0dc9346571a34c85d40fdbb01b5
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Wed, 10 Nov 2021 23:35:13 +0100
       
       Implement J, I, and ^
       
       Diffstat:
         M buffer.c                            |      15 +++++++++++++++
         M buffer.h                            |       1 +
         M keys_basic.c                        |     108 +++++++++++++++++++++++++++++--
         M keys_basic_config.h                 |       6 ++++++
       
       4 files changed, 126 insertions(+), 4 deletions(-)
       ---
   DIR diff --git a/buffer.c b/buffer.c
       t@@ -1123,6 +1123,21 @@ line_next_bigword(ledit_line *line, int byte, int char_index, int wrapped_line, 
                return -1;
        }
        
       +int
       +ledit_line_next_non_whitespace(ledit_line *line, int byte) {
       +        int c, nattrs;
       +        c = line_byte_to_char(line, byte);
       +        int cur_byte = byte;
       +        const PangoLogAttr *attrs =
       +            pango_layout_get_log_attrs_readonly(line->layout, &nattrs);
       +        for (; c < nattrs; c++) {
       +                if (!attrs[c].is_white)
       +                        return cur_byte;
       +                cur_byte = ledit_line_next_utf8(line, cur_byte);
       +        }
       +        return line->len;
       +}
       +
        /* FIXME: document that word and bigword are a bit weird because word uses unicode semantics */
        
        #define GEN_NEXT_WORD(name, func)                                                                               \
   DIR diff --git a/buffer.h b/buffer.h
       t@@ -158,3 +158,4 @@ void ledit_buffer_scroll_to_pos_top(ledit_buffer *buffer, int line, int byte);
        void ledit_buffer_scroll_to_pos_bottom(ledit_buffer *buffer, int line, int byte);
        /* FIXME: just make generic sort range */
        void ledit_buffer_sort_selection(int *line1, int *byte1, int *line2, int *byte2);
       +int ledit_line_next_non_whitespace(ledit_line *line, int byte);
   DIR diff --git a/keys_basic.c b/keys_basic.c
       t@@ -387,7 +387,8 @@ delete_range(
            ledit_buffer *buffer,
            int line_based, int selected,
            int line_index1, int byte_index1,
       -    int line_index2, int byte_index2, int copy_to_buffer) {
       +    int line_index2, int byte_index2,
       +    int copy_to_buffer) {
                (void)selected; /* FIXME */
                if (copy_to_buffer && !paste_buffer)
                        paste_buffer = txtbuf_new();
       t@@ -1645,18 +1646,117 @@ cursor_up(ledit_buffer *buffer, char *text, int len) {
        }
        
        static struct action
       +join_lines(ledit_buffer *buffer, char *text, int len) {
       +        (void)text;
       +        (void)len;
       +        int num = get_key_repeat();
       +        if (num == -1)
       +                return err_invalid_key(buffer);
       +        if (num == 0)
       +                num = 1;
       +        int start_group = 1;
       +        ledit_line *ll1, *ll2;
       +        int cur_line = buffer->cur_line;
       +        /* FIXME: have a general tmp buf for everyone to use */
       +        txtbuf *buf = txtbuf_new();
       +        int 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 == buffer->lines_num - 1)
       +                        break;
       +                /* getting cur line again should be unnecessary, but
       +                   I'll just leave it in case I change the way lines
       +                   are stored later */
       +                ll1 = ledit_buffer_get_line(buffer, cur_line);
       +                oldlen = ll1->len;
       +                ll2 = ledit_buffer_get_line(buffer, cur_line + 1);
       +                /* FIXME: truncate whitespace to one space */
       +                ledit_buffer_delete_range(
       +                    buffer, 0,
       +                    cur_line, ll1->len, cur_line + 1, 0,
       +                    NULL, NULL, &del_range, buf
       +                );
       +                cur_range.byte1 = buffer->cur_index;
       +                cur_range.byte2 = buffer->cur_index =
       +                    ledit_buffer_get_legal_normal_pos(buffer, buffer->cur_line, oldlen);
       +                ledit_push_undo_delete(
       +                    buffer->undo, buf, del_range, cur_range,
       +                    start_group, buffer->common->mode
       +                );
       +                start_group = 0;
       +        }
       +        ledit_buffer_set_line_cursor_attrs(
       +            buffer, buffer->cur_line, buffer->cur_index
       +        );
       +        txtbuf_destroy(buf);
       +        finalize_repetition_stack();
       +        return (struct action){ACTION_NONE, NULL};
       +}
       +
       +static struct action
       +insert_at_beginning(ledit_buffer *buffer, char *text, int len) {
       +        if (!key_stack_empty())
       +                return err_invalid_key(buffer);
       +        enter_insert(buffer, text, len);
       +        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 *pl = pango_layout_get_line_readonly(ll->layout, sli);
       +        push_undo_empty_insert(buffer, buffer->cur_line, buffer->cur_index, 1);
       +        buffer->cur_index = pl->start_index;
       +        ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line);
       +        return (struct action){ACTION_NONE, NULL};
       +}
       +
       +static struct action
       +cursor_to_first_non_ws(ledit_buffer *buffer, char *text, int len) {
       +        (void)text;
       +        (void)len;
       +        int x, sli;
       +        motion_callback cb;
       +        int num = get_key_repeat_and_motion_cb(&cb);
       +        if (num != 0)
       +                return err_invalid_key(buffer);
       +        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 *pl = pango_layout_get_line_readonly(ll->layout, sli);
       +        int new_index = ledit_line_next_non_whitespace(ll, pl->start_index);
       +        /* next non-whitespace might be on next softline */
       +        if (new_index >= pl->start_index + pl->length) {
       +                new_index = ledit_buffer_prev_cursor_pos(
       +                    buffer, buffer->cur_line, pl->start_index + pl->length, 1
       +                );
       +        }
       +        if (cb != NULL) {
       +                cb(buffer, buffer->cur_line, new_index, KEY_MOTION_CHAR);
       +        } else {
       +                buffer->cur_index = new_index;
       +                ledit_buffer_set_line_cursor_attrs(
       +                    buffer, buffer->cur_line, buffer->cur_index
       +                );
       +                discard_repetition_stack();
       +        }
       +        return (struct action){ACTION_NONE, NULL};
       +}
       +
       +static struct action
        cursor_to_beginning(ledit_buffer *buffer, char *text, int len) {
                (void)text;
                (void)len;
       +        int x, sli;
                motion_callback cb;
                int num = get_key_repeat_and_motion_cb(&cb);
       -        if (num == -1)
       +        if (num != 0)
                        return err_invalid_key(buffer);
                /* FIXME: should anything be done with num? */
       +        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 *pl = pango_layout_get_line_readonly(ll->layout, sli);
                if (cb != NULL) {
       -                cb(buffer, buffer->cur_line, 0, KEY_MOTION_CHAR);
       +                cb(buffer, buffer->cur_line, pl->start_index, KEY_MOTION_CHAR);
                } else {
       -                buffer->cur_index = 0;
       +                buffer->cur_index = pl->start_index;
                        if (buffer->common->mode == VISUAL) {
                                ledit_buffer_set_selection(
                                    buffer,
   DIR diff --git a/keys_basic_config.h b/keys_basic_config.h
       t@@ -90,6 +90,9 @@ static struct action delete_chars_backwards(ledit_buffer *buffer, char *text, in
        static struct action yank(ledit_buffer *buffer, char *text, int len);
        static struct action yank_lines(ledit_buffer *buffer, char *text, int len);
        static struct action replace(ledit_buffer *buffer, char *text, int len);
       +static struct action cursor_to_first_non_ws(ledit_buffer *buffer, char *text, int len);
       +static struct action join_lines(ledit_buffer *buffer, char *text, int len);
       +static struct action insert_at_beginning(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@@ -158,6 +161,8 @@ static struct key keys_en[] = {
                {"b",  0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBERALLOWED, &prev_word},
                {"B",  0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBERALLOWED, &prev_bigword},
                {"G",  0, 0, NORMAL, KEY_ANY, KEY_NUMBERALLOWED, &move_to_line},
       +        {"J",  0, 0, NORMAL, KEY_ANY, KEY_NUMBERALLOWED, &join_lines},
       +        {"I",  0, 0, NORMAL, KEY_ANY, KEY_ANY, &insert_at_beginning},
                {"p",  0, 0, NORMAL, KEY_ANY, KEY_ANY, &paste_normal},
                {"P",  0, 0, NORMAL, KEY_ANY, KEY_ANY, &paste_normal_backwards},
                {"A",  0, 0, NORMAL, KEY_ANY, KEY_ANY, &append_after_eol},
       t@@ -169,6 +174,7 @@ static struct key keys_en[] = {
                {"C",  0, 0, NORMAL, KEY_ANY, KEY_ANY, &change_to_eol},
                {"D",  0, 0, NORMAL, KEY_ANY, KEY_ANY, &delete_to_eol},
                {"r",  0, 0, NORMAL, KEY_ANY, KEY_ANY, &replace},
       +        {"^",  0, 0, NORMAL, KEY_ANY, KEY_ANY, &cursor_to_first_non_ws},
                {"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},