URI: 
       tAbstract text operations a bit - 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 0e379b0cf1ba991e2024b2541a9d5d4f80068d5b
   DIR parent e181351df92df2596bd48578667ce195f4cf34d0
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Sat,  5 Jun 2021 20:16:59 +0200
       
       Abstract text operations a bit
       
       Diffstat:
         M buffer.c                            |     128 ++++++++++++++++++++++++++-----
         M buffer.h                            |      16 ++++++++++++++++
         M ledit.c                             |      82 +++++++++----------------------
       
       3 files changed, 149 insertions(+), 77 deletions(-)
       ---
   DIR diff --git a/buffer.c b/buffer.c
       t@@ -1,6 +1,7 @@
        /* FIXME: shrink buffers when text length less than a fourth of the size */
        
        #include <string.h>
       +#include <assert.h>
        
        #include <X11/Xlib.h>
        #include <X11/Xutil.h>
       t@@ -101,6 +102,18 @@ ledit_wipe_line_cursor_attrs(ledit_buffer *buffer, int line) {
        }
        
        void
       +ledit_insert_text_from_line(
       +    ledit_buffer *buffer,
       +    int dst_line, int dst_index,
       +    int src_line, int src_index, int src_len) {
       +        assert(dst_line != src_line);
       +        ledit_line *ll = ledit_get_line(buffer, src_line);
       +        if (src_len == -1)
       +                src_len = ll->len - src_index;
       +        ledit_insert_text(buffer, dst_line, dst_index, ll->text, src_len);
       +}
       +
       +void
        ledit_insert_text(ledit_buffer *buffer, int line_index, int index, char *text, int len) {
                ledit_line *line = &buffer->lines[line_index];
                if (len == -1)
       t@@ -333,22 +346,109 @@ ledit_line_visible(ledit_buffer *buffer, int index) {
                       line->y_offset + line->h > buffer->display_offset;
        }
        
       +/* get needed length of text range, including newlines
       + * - NUL is not included
       + * - if the last range ends at the end of a line, the newline is *not* included
       + * - the range must be sorted already */
       +size_t
       +ledit_textlen(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2) {
       +        assert(line1 < line2 || (line1 == line2 && byte1 <= byte2));
       +        size_t len = 0;
       +        ledit_line *ll = ledit_get_line(buffer, line1);
       +        if (line1 == line2) {
       +                len = byte2 - byte1;
       +        } else {
       +                /* + 1 for newline */
       +                len = ll->len - byte1 + byte2 + 1;
       +                for (int i = line1 + 1; i < line2; i++) {
       +                        ll = ledit_get_line(buffer, i);
       +                        len += ll->len + 1;
       +                }
       +        }
       +        return len;
       +}
       +
       +/* copy text range into given buffer
       + * - dst is null-terminated
       + * - dst must be large enough to contain the text and NUL
       + * - the range must be sorted already */
       +void
       +ledit_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2, int byte2) {
       +        assert(line1 < line2 || (line1 == line2 && byte1 <= byte2));
       +        ledit_line *ll1 = ledit_get_line(buffer, line1);
       +        ledit_line *ll2 = ledit_get_line(buffer, line2);
       +        if (line1 == line2) {
       +                memcpy(dst, ll1->text + byte1, byte2 - byte1);
       +                dst[byte2 - byte1] = '\0';
       +        } else {
       +                size_t cur_pos = 0;
       +                memcpy(dst, ll1->text + byte1, ll1->len - byte1);
       +                cur_pos += ll1->len - byte1;
       +                dst[cur_pos] = '\n';
       +                cur_pos++;
       +                for (int i = line1 + 1; i < line2; i++) {
       +                        ledit_line *ll = ledit_get_line(buffer, i);
       +                        memcpy(dst + cur_pos, ll->text, ll->len);
       +                        cur_pos += ll->len;
       +                        dst[cur_pos] = '\n';
       +                        cur_pos++;
       +                }
       +                memcpy(dst + cur_pos, ll2->text, byte2);
       +                cur_pos += byte2;
       +                dst[cur_pos] = '\0';
       +        }
       +}
       +
       +/* copy text range into given buffer and resize it if necessary
       + * - *dst is reallocated and *alloc adjusted if the text doesn't fit
       + * - *dst is null-terminated
       + * - the range must be sorted already
       + * - returns the length of the text, not including the NUL */
       +size_t
       +ledit_copy_text_with_resize(
       +    ledit_buffer *buffer,
       +    char **dst, size_t *alloc,
       +    int line1, int byte1,
       +    int line2, int byte2) {
       +        assert(line1 < line2 || (line1 == line2 && byte1 <= byte2));
       +        size_t len = ledit_textlen(buffer, line1, byte1, line2, byte2);
       +        /* len + 1 because of nul */
       +        if (len + 1 > *alloc) {
       +                *alloc = *alloc * 2 > len + 1 ? *alloc * 2 : len + 1;
       +                *dst = ledit_realloc(*dst, *alloc);
       +        }
       +        ledit_copy_text(buffer, *dst, line1, byte1, line2, byte2);
       +        return len;
       +}
       +
       +int
       +ledit_prev_utf8(ledit_line *line, int index) {
       +        int i = index - 1;
       +        /* find valid utf8 char - this probably needs to be improved */
       +        while (i > 0 && ((line->text[i] & 0xC0) == 0x80))
       +                i--;
       +        return i;
       +}
       +
       +int
       +ledit_next_utf8(ledit_line *line, int index) {
       +        int i = index + 1;
       +        while (i < line->len && ((line->text[i] & 0xC0) == 0x80))
       +                i++;
       +        return i;
       +}
       +
        int
        ledit_delete_unicode_char(ledit_buffer *buffer, int line_index, int byte_index, int dir) {
                ledit_line *l = ledit_get_line(buffer, line_index);
                int new_index = byte_index;
                if (dir < 0) {
       -                int i = byte_index - 1;
       -                /* find valid utf8 char - this probably needs to be improved */
       -                while (i > 0 && ((l->text[i] & 0xC0) == 0x80))
       -                        i--;
       +                int i = ledit_prev_utf8(l, byte_index);
                        memmove(l->text + i, l->text + byte_index, l->len - byte_index);
                        l->len -= byte_index - i;
                        new_index = i;
                } else {
       -                int i = byte_index + 1;
       -                while (i < l->len && ((l->text[i] & 0xC0) == 0x80))
       -                        i++;
       +                int i = ledit_next_utf8(l, byte_index);
                        memmove(l->text + byte_index, l->text + i, l->len - i);
                        l->len -= i - byte_index;
                }
       t@@ -410,11 +510,7 @@ ledit_x_softline_to_pos(ledit_line *line, int x, int softline, int *pos_ret) {
                if (line->parent_buffer->state->mode == INSERT) {
                        while (trailing > 0) {
                                trailing--;
       -                        (*pos_ret)++;
       -                        /* utf8 stuff */
       -                        while (*pos_ret < line->len &&
       -                               ((line->text[*pos_ret] & 0xC0) == 0x80))
       -                                (*pos_ret)++;
       +                        *pos_ret = ledit_next_utf8(line, *pos_ret);
                        }
                }
        }
       t@@ -429,14 +525,10 @@ ledit_get_legal_normal_pos(ledit_buffer *buffer, int line, int pos) {
                        const PangoLogAttr *attrs =
                            pango_layout_get_log_attrs_readonly(final_line->layout, &nattrs);
                        int cur = nattrs - 2;
       -                ret--;
       -                while (ret > 0 && ((final_line->text[ret] & 0xC0) == 0x80))
       -                        ret--;
       +                ret = ledit_prev_utf8(final_line, ret);
                        while (ret > 0 && cur > 0 && !attrs[cur].is_cursor_position) {
                                cur--;
       -                        ret--;
       -                        while (ret > 0 && ((final_line->text[ret] & 0xC0) == 0x80))
       -                                ret--;
       +                        ret = ledit_prev_utf8(final_line, ret);
                        }
                }
                return ret;
   DIR diff --git a/buffer.h b/buffer.h
       t@@ -66,3 +66,19 @@ void ledit_delete_range(
        );
        void ledit_pos_to_x_softline(ledit_line *line, int pos, int *x_ret, int *softline_ret);
        void ledit_x_softline_to_pos(ledit_line *line, int x, int softline, int *pos_ret);
       +int ledit_next_utf8(ledit_line *line, int index);
       +int ledit_prev_utf8(ledit_line *line, int index);
       +size_t ledit_textlen(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2);
       +void ledit_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2, int byte2);
       +size_t ledit_copy_text_with_resize(
       +    ledit_buffer *buffer,
       +    char **dst, size_t *alloc,
       +    int line1, int byte1,
       +    int line2, int byte2
       +);
       +void
       +ledit_insert_text_from_line(
       +    ledit_buffer *buffer,
       +    int dst_line, int dst_index,
       +    int src_line, int src_index, int src_len
       +);
   DIR diff --git a/ledit.c b/ledit.c
       t@@ -707,7 +707,7 @@ mainloop(void) {
                        fprintf(stderr, "XKB not supported.");
                        exit(1);
                }
       -        printf("XKB (%d.%d) supported.\n", major, minor);
       +        /*printf("XKB (%d.%d) supported.\n", major, minor);*/
                /* This should select the events when the keyboard mapping changes.
                 * When e.g. 'setxkbmap us' is executed, two events are sent, but I
                 * haven't figured out how to change that. When the xkb layout
       t@@ -821,10 +821,12 @@ setup(int argc, char *argv[]) {
                /* based on http://wili.cc/blog/xdbe.html */
                int major, minor;
                if (XdbeQueryExtension(state.dpy, &major, &minor)) {
       +                /*
                        printf(
                            "Xdbe (%d.%d) supported, using double buffering.\n",
                            major, minor
                        );
       +                */
                        int num_screens = 1;
                        Drawable screens[] = { DefaultRootWindow(state.dpy) };
                        XdbeScreenVisualInfo *info = XdbeGetVisualInfo(
       t@@ -1116,12 +1118,9 @@ xy_to_line_byte(int x, int y, int *line_ret, int *byte_ret) {
                                    x * PANGO_SCALE, (int)(pos - h) * PANGO_SCALE,
                                    &index, &trailing
                                );
       -                        /* FIXME: make this a separate, reusable function */
                                while (trailing > 0) {
                                        trailing--;
       -                                index++;
       -                                while (index < line->len && ((line->text[index] & 0xC0) == 0x80))
       -                                        index++;
       +                                index = ledit_next_utf8(line, index);
                                }
                                *line_ret = i;
                                *byte_ret = index;
       t@@ -1153,45 +1152,10 @@ sort_selection(int *line1, int *byte1, int *line2, int *byte2) {
        /* lines and bytes need to be sorted already! */
        static void
        copy_selection_to_x_primary(int line1, int byte1, int line2, int byte2) {
       -        size_t len = 0;
       -        ledit_line *ll1 = ledit_get_line(buffer, line1);
       -        ledit_line *ll2 = ledit_get_line(buffer, line2);
       -        if (line1 == line2) {
       -                len = byte2 - byte1;
       -        } else {
       -                /* + 1 for newline */
       -                len = ll1->len - byte1 + byte2 + 1;
       -                for (int i = line1 + 1; i < line2; i++) {
       -                        ledit_line *ll = ledit_get_line(buffer, i);
       -                        len += ll->len + 1;
       -                }
       -        }
       -        len += 1; /* nul */
       -        if (len > xsel.primary_alloc) {
       -                /* FIXME: maybe allocate a bit more */
       -                xsel.primary = ledit_realloc(xsel.primary, len);
       -                xsel.primary_alloc = len;
       -        }
       -        if (line1 == line2) {
       -                memcpy(xsel.primary, ll1->text + byte1, byte2 - byte1);
       -                xsel.primary[byte2 - byte1] = '\0';
       -        } else {
       -                size_t cur_pos = 0;
       -                memcpy(xsel.primary, ll1->text + byte1, ll1->len - byte1);
       -                cur_pos += ll1->len - byte1;
       -                xsel.primary[cur_pos] = '\n';
       -                cur_pos++;
       -                for (int i = line1 + 1; i < line2; i++) {
       -                        ledit_line *ll = ledit_get_line(buffer, i);
       -                        memcpy(xsel.primary + cur_pos, ll->text, ll->len);
       -                        cur_pos += ll->len;
       -                        xsel.primary[cur_pos] = '\n';
       -                        cur_pos++;
       -                }
       -                memcpy(xsel.primary + cur_pos, ll2->text, byte2);
       -                cur_pos += byte2;
       -                xsel.primary[cur_pos] = '\0';
       -        }
       +        (void)ledit_copy_text_with_resize(
       +            buffer, &xsel.primary, &xsel.primary_alloc,
       +            line1, byte1, line2, byte2
       +        );
                XSetSelectionOwner(state.dpy, XA_PRIMARY, state.win, CurrentTime);
                /*
                FIXME
       t@@ -1378,12 +1342,10 @@ backspace(void) {
                } else if (buffer->cur_index == 0) {
                        if (buffer->cur_line != 0) {
                                ledit_line *l1 = ledit_get_line(buffer, buffer->cur_line - 1);
       -                        ledit_line *l2 = ledit_get_line(buffer, buffer->cur_line);
                                int old_len = l1->len;
       -                        ledit_insert_text_with_newlines(
       -                            buffer, buffer->cur_line - 1,
       -                            l1->len, l2->text, l2->len,
       -                            NULL, NULL
       +                        ledit_insert_text_from_line(
       +                            buffer, buffer->cur_line - 1, l1->len,
       +                            buffer->cur_line, 0, -1
                                );
                                ledit_delete_line_entry(buffer, buffer->cur_line);
                                buffer->cur_line--;
       t@@ -1404,14 +1366,10 @@ delete_key(void) {
                        /* NOP */
                } else if (buffer->cur_index == cur_line->len) {
                        if (buffer->cur_line != buffer->lines_num - 1) {
       -                        ledit_line *next_line = ledit_get_line(
       -                            buffer, buffer->cur_line + 1
       -                        );
                                int old_len = cur_line->len;
       -                        ledit_insert_text_with_newlines(
       +                        ledit_insert_text_from_line(
                                    buffer, buffer->cur_line, cur_line->len,
       -                            next_line->text, next_line->len,
       -                            NULL, NULL
       +                            buffer->cur_line + 1, 0, -1
                                );
                                ledit_delete_line_entry(buffer, buffer->cur_line + 1);
                                buffer->cur_index = old_len;
       t@@ -1463,10 +1421,7 @@ move_cursor_left_right(int dir) {
                /* FIXME: spaces at end of softlines are weird in normal mode */
                while (trailing > 0) {
                        trailing--;
       -                new_index++;
       -                while (new_index < cur_line->len &&
       -                       ((cur_line->text[new_index] & 0xC0) == 0x80))
       -                        new_index++;
       +                new_index = ledit_next_utf8(cur_line, new_index);
                }
                if (new_index < 0)
                        new_index = 0;
       t@@ -1725,6 +1680,14 @@ end_lineedit(void) {
                }
        }
        
       +static void
       +show_line(void) {
       +        int len = snprintf(NULL, 0, "Line %d of %d", buffer->cur_line + 1, buffer->lines_num);
       +        char *str = ledit_malloc(len + 1);
       +        snprintf(str, len + 1, "Line %d of %d", buffer->cur_line + 1, buffer->lines_num);
       +        show_message(str, len);
       +}
       +
        /* FIXME: maybe sort these and use binary search */
        static struct key keys_en[] = {
                {NULL, 0, XK_BackSpace, INSERT, KEY_ANY, KEY_ANY, &backspace},
       t@@ -1757,6 +1720,7 @@ static struct key keys_en[] = {
                {"o",  0, 0, VISUAL, KEY_ANY, KEY_ANY, &switch_selection_end},
                {"c",  ControlMask, 0, INSERT|VISUAL, KEY_ANY, KEY_ANY, &clipcopy},
                {"v",  ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &clippaste},
       +        {"g",  ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &show_line},
                {":",  0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_commandedit},
                {"?",  0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_searchedit_backward},
                {"/",  0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_searchedit_forward},