URI: 
       tImprove bottom bar text input - 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 f46f28e4d372daa0353489d65a29078e69fe0376
   DIR parent a52f6845ba83e6df5d60d22844b1a2546ca97af1
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Fri, 12 Nov 2021 23:31:39 +0100
       
       Improve bottom bar text input
       
       Diffstat:
         M buffer.c                            |       9 +++++++++
         M keys_basic.c                        |       4 +++-
         M keys_command.c                      |      64 +++++++++++++++++++++++++++++--
         M keys_command_config.h               |      28 ++++++++++++++++++++++++++++
         M undo.c                              |      12 ++++++++++++
         M window.c                            |     123 +++++++++++++++++++++++++++++--
         M window.h                            |       5 +++++
       
       7 files changed, 233 insertions(+), 12 deletions(-)
       ---
   DIR diff --git a/buffer.c b/buffer.c
       t@@ -2239,10 +2239,19 @@ ledit_buffer_redo(ledit_buffer *buffer) {
        static void
        paste_callback(void *data, char *text, int len) {
                ledit_buffer *buffer = (ledit_buffer *)data;
       +        txtbuf ins_buf = {.text = text, .len = len, .cap = len};
       +        ledit_range cur_range, ins_range;
       +        cur_range.line1 = ins_range.line1 = buffer->cur_line;
       +        cur_range.byte1 = ins_range.byte1 = buffer->cur_index;
                ledit_buffer_insert_text_with_newlines(
                    buffer, buffer->cur_line, buffer->cur_index,
                    text, len, &buffer->cur_line, &buffer->cur_index
                );
       +        cur_range.line2 = ins_range.line2 = buffer->cur_line;
       +        cur_range.byte2 = ins_range.byte2 = buffer->cur_index;
       +        ledit_push_undo_insert(
       +            buffer->undo, &ins_buf, ins_range, cur_range, 1, buffer->common->mode
       +        );
        }
        
        /* FIXME: guard against buffer being destroyed before paste callback is nulled */
   DIR diff --git a/keys_basic.c b/keys_basic.c
       t@@ -421,7 +421,6 @@ insert_text(
            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;
                if (cur_line1 >= 0 && cur_index1 >= 0) {
       t@@ -1804,6 +1803,7 @@ enter_commandedit(ledit_buffer *buffer, char *text, int len) {
                (void)text;
                (void)len;
                ledit_window_set_bottom_bar_text(buffer->window, ":", -1);
       +        ledit_window_set_bottom_bar_min_pos(buffer->window, 1);
                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);
       t@@ -1816,6 +1816,7 @@ enter_searchedit_forward(ledit_buffer *buffer, char *text, int len) {
                (void)text;
                (void)len;
                ledit_window_set_bottom_bar_text(buffer->window, "/", -1);
       +        ledit_window_set_bottom_bar_min_pos(buffer->window, 1);
                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);
       t@@ -1828,6 +1829,7 @@ enter_searchedit_backward(ledit_buffer *buffer, char *text, int len) {
                (void)text;
                (void)len;
                ledit_window_set_bottom_bar_text(buffer->window, "?", -1);
       +        ledit_window_set_bottom_bar_min_pos(buffer->window, 1);
                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);
   DIR diff --git a/keys_command.c b/keys_command.c
       t@@ -26,6 +26,8 @@
        #include "keys_command.h"
        #include "keys_command_config.h"
        
       +/* FIXME: history for search and commands */
       +
        /* FIXME: THIS WON'T WORK WHEN THERE ARE MULTIPLE BUFFERS! */
        /* this must first be set by caller before jumping to key handler */
        static enum ledit_command_type cur_type;
       t@@ -244,10 +246,57 @@ edit_insert_text(ledit_buffer *buffer, char *key_text, int len) {
        }
        
        static int
       +edit_cursor_to_end(ledit_buffer *buffer, char *key_text, int len) {
       +        (void)key_text;
       +        (void)len;
       +        ledit_window_bottom_bar_cursor_to_end(buffer->window);
       +        return 1;
       +}
       +
       +static int
       +edit_cursor_to_beginning(ledit_buffer *buffer, char *key_text, int len) {
       +        (void)key_text;
       +        (void)len;
       +        ledit_window_bottom_bar_cursor_to_beginning(buffer->window);
       +        return 1;
       +}
       +
       +static int
       +edit_cursor_left(ledit_buffer *buffer, char *key_text, int len) {
       +        (void)key_text;
       +        (void)len;
       +        ledit_window_move_bottom_bar_cursor(buffer->window, -1);
       +        return 1;
       +}
       +
       +static int
       +edit_cursor_right(ledit_buffer *buffer, char *key_text, int len) {
       +        (void)key_text;
       +        (void)len;
       +        ledit_window_move_bottom_bar_cursor(buffer->window, 1);
       +        return 1;
       +}
       +
       +static int
       +edit_backspace(ledit_buffer *buffer, char *key_text, int len) {
       +        (void)key_text;
       +        (void)len;
       +        ledit_window_delete_bottom_bar_char(buffer->window, -1);
       +        return 1;
       +}
       +
       +static int
       +edit_delete(ledit_buffer *buffer, char *key_text, int len) {
       +        (void)key_text;
       +        (void)len;
       +        ledit_window_delete_bottom_bar_char(buffer->window, 1);
       +        return 1;
       +}
       +
       +static int
        edit_submit(ledit_buffer *buffer, char *key_text, int len) {
                (void)key_text;
                (void)len;
       -        ledit_buffer_set_mode(buffer, NORMAL);
                ledit_window_set_bottom_bar_text_shown(buffer->window, 0);
                /* FIXME: this is hacky */
                return handle_cmd(buffer, ledit_window_get_bottom_bar_text(buffer->window) + 1, -1);
       t@@ -278,7 +327,6 @@ static int
        editsearch_submit(ledit_buffer *buffer, char *key_text, int len) {
                (void)key_text;
                (void)len;
       -        ledit_buffer_set_mode(buffer, NORMAL);
                ledit_window_set_bottom_bar_text_shown(buffer->window, 0);
                ledit_set_search_forward(ledit_window_get_bottom_bar_text(buffer->window) + 1);
                search_next(buffer);
       t@@ -289,13 +337,21 @@ static int
        editsearchb_submit(ledit_buffer *buffer, char *key_text, int len) {
                (void)key_text;
                (void)len;
       -        ledit_buffer_set_mode(buffer, NORMAL);
                ledit_window_set_bottom_bar_text_shown(buffer->window, 0);
                ledit_set_search_backward(ledit_window_get_bottom_bar_text(buffer->window) + 1);
                search_next(buffer);
                return 0;
        }
        
       +static int
       +edit_discard(ledit_buffer *buffer, char *key_text, int len) {
       +        (void)buffer;
       +        (void)key_text;
       +        (void)len;
       +        ledit_window_set_bottom_bar_text_shown(buffer->window, 0);
       +        return 0;
       +}
       +
        struct action
        ledit_command_key_handler(ledit_buffer *buffer, XEvent *event, int lang_index) {
                char buf[64];
       t@@ -305,7 +361,7 @@ ledit_command_key_handler(ledit_buffer *buffer, XEvent *event, int lang_index) {
                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 grabkey = 0;
       +        int grabkey = 1;
                for (int i = 0; i < num_keys; i++) {
                        if (cur_keys[i].text) {
                                if (n > 0 &&
   DIR diff --git a/keys_command_config.h b/keys_command_config.h
       t@@ -3,9 +3,16 @@ static int substitute_yes_all(ledit_buffer *buffer, char *key_text, int len);
        static int substitute_no(ledit_buffer *buffer, char *key_text, int len);
        static int substitute_no_all(ledit_buffer *buffer, char *key_text, int len);
        static int edit_insert_text(ledit_buffer *buffer, char *key_text, int len);
       +static int edit_cursor_left(ledit_buffer *buffer, char *key_text, int len);
       +static int edit_cursor_right(ledit_buffer *buffer, char *key_text, int len);
       +static int edit_cursor_to_end(ledit_buffer *buffer, char *key_text, int len);
       +static int edit_cursor_to_beginning(ledit_buffer *buffer, char *key_text, int len);
       +static int edit_backspace(ledit_buffer *buffer, char *key_text, int len);
       +static int edit_delete(ledit_buffer *buffer, char *key_text, int len);
        static int edit_submit(ledit_buffer *buffer, char *key_text, int len);
        static int editsearch_submit(ledit_buffer *buffer, char *key_text, int len);
        static int editsearchb_submit(ledit_buffer *buffer, char *key_text, int len);
       +static int edit_discard(ledit_buffer *buffer, char *key_text, int len);
        
        struct key {
                char *text;                                /* for keys that correspond with text */
       t@@ -24,6 +31,27 @@ static struct key keys_en[] = {
                {NULL, 0, XK_Return, CMD_EDIT, &edit_submit},
                {NULL, 0, XK_Return, CMD_EDITSEARCH, &editsearch_submit},
                {NULL, 0, XK_Return, CMD_EDITSEARCHB, &editsearchb_submit},
       +        {NULL, 0, XK_Left, CMD_EDIT, &edit_cursor_left},
       +        {NULL, 0, XK_Left, CMD_EDITSEARCH, &edit_cursor_left},
       +        {NULL, 0, XK_Left, CMD_EDITSEARCHB, &edit_cursor_left},
       +        {NULL, 0, XK_Right, CMD_EDIT, &edit_cursor_right},
       +        {NULL, 0, XK_Right, CMD_EDITSEARCH, &edit_cursor_right},
       +        {NULL, 0, XK_Right, CMD_EDITSEARCHB, &edit_cursor_right},
       +        {NULL, 0, XK_BackSpace, CMD_EDIT, &edit_backspace},
       +        {NULL, 0, XK_BackSpace, CMD_EDITSEARCH, &edit_backspace},
       +        {NULL, 0, XK_BackSpace, CMD_EDITSEARCHB, &edit_backspace},
       +        {NULL, 0, XK_Delete, CMD_EDIT, &edit_delete},
       +        {NULL, 0, XK_Delete, CMD_EDITSEARCH, &edit_delete},
       +        {NULL, 0, XK_Delete, CMD_EDITSEARCHB, &edit_delete},
       +        {NULL, 0, XK_End, CMD_EDIT, &edit_cursor_to_end},
       +        {NULL, 0, XK_End, CMD_EDITSEARCH, &edit_cursor_to_end},
       +        {NULL, 0, XK_End, CMD_EDITSEARCHB, &edit_cursor_to_end},
       +        {NULL, 0, XK_Home, CMD_EDIT, &edit_cursor_to_beginning},
       +        {NULL, 0, XK_Home, CMD_EDITSEARCH, &edit_cursor_to_beginning},
       +        {NULL, 0, XK_Home, CMD_EDITSEARCHB, &edit_cursor_to_beginning},
       +        {NULL, 0, XK_Escape, CMD_EDIT, &edit_discard},
       +        {NULL, 0, XK_Escape, CMD_EDITSEARCH, &edit_discard},
       +        {NULL, 0, XK_Escape, CMD_EDITSEARCHB, &edit_discard},
                {"", 0, 0, CMD_EDIT, &edit_insert_text},
                {"", 0, 0, CMD_EDITSEARCH, &edit_insert_text},
                {"", 0, 0, CMD_EDITSEARCHB, &edit_insert_text}
   DIR diff --git a/undo.c b/undo.c
       t@@ -12,6 +12,18 @@
        #include "cache.h"
        #include "undo.h"
        
       +/* FIXME: more sanity checks in case text is
       +   inserted/deleted without adding to undo stack */
       +/* FIXME: cursor positions can be a bit weird when
       +   undo is used across different insert sessions in
       +   insert mode - e.g. if some text is inserted, then
       +   'o' is used in normal mode to append a new line and
       +   type some text, the cursor position at a certain
       +   undo position will differ in insert mode depending
       +   on whether it was reached with undo or redo since
       +   'o' saves the position at which it was pressed,
       +   not the position of the last insert */
       +
        enum operation {
                UNDO_INSERT,
                UNDO_DELETE
   DIR diff --git a/window.c b/window.c
       t@@ -18,12 +18,13 @@
        #include "window.h"
        #include "util.h"
        
       +/* FIXME: Everything to do with the bottom bar is extremely hacky */
        struct bottom_bar {
                /* FIXME: encapsulate layout, width, draw a bit */
                PangoLayout *mode;
                ledit_draw *mode_draw;
                int mode_w, mode_h;
       -        PangoLayout *ruler;
       +        PangoLayout *ruler; /* not implemented yet */
                ledit_draw *ruler_draw;
                int ruler_w, ruler_h;
                PangoLayout *line;
       t@@ -32,6 +33,7 @@ struct bottom_bar {
                char *line_text;
                int line_alloc, line_len;
                int line_cur_pos;
       +        int min_pos; /* minimum position cursor can be at */
        };
        
        /* clipboard handling largely stolen from st (simple terminal) */
       t@@ -72,7 +74,7 @@ recalc_text_size(ledit_window *window) {
        void
        ledit_window_insert_bottom_bar_text(ledit_window *window, char *text, int len) {
                assert(len >= -1);
       -        assert(window->bb->line_cur_pos <= window->bb->line_alloc);
       +        assert(window->bb->line_cur_pos <= window->bb->line_len);
        
                if (len == -1)
                        len = strlen(text);
       t@@ -102,6 +104,90 @@ ledit_window_insert_bottom_bar_text(ledit_window *window, char *text, int len) {
        }
        
        void
       +ledit_window_move_bottom_bar_cursor(ledit_window *window, int movement) {
       +        assert(window->bb->line_cur_pos <= window->bb->line_len);
       +        int trailing = 0;
       +        int new_index = window->bb->line_cur_pos;
       +        pango_layout_move_cursor_visually(
       +            window->bb->line, TRUE,
       +            new_index, trailing, movement,
       +            &new_index, &trailing
       +        );
       +        while (trailing > 0) {
       +                trailing--;
       +                /* FIXME: move to common/util */
       +                new_index++;
       +                while (new_index < window->bb->line_len &&
       +                       (window->bb->line_text[new_index] & 0xC0) == 0x80)
       +                        new_index++;
       +        }
       +        if (new_index < window->bb->min_pos)
       +                new_index = window->bb->min_pos;
       +        if (new_index > window->bb->line_len)
       +                new_index = window->bb->line_len;
       +        window->bb->line_cur_pos = new_index;
       +}
       +
       +void
       +ledit_window_set_bottom_bar_min_pos(ledit_window *window, int pos) {
       +        window->bb->min_pos = pos;
       +}
       +
       +void
       +ledit_window_bottom_bar_cursor_to_beginning(ledit_window *window) {
       +        window->bb->line_cur_pos = window->bb->min_pos;
       +}
       +
       +void
       +ledit_window_bottom_bar_cursor_to_end(ledit_window *window) {
       +        window->bb->line_cur_pos = window->bb->line_len;
       +}
       +
       +/* FIXME: respect PangoLogAttr.backspace_deletes_character */
       +void
       +ledit_window_delete_bottom_bar_char(ledit_window *window, int dir) {
       +        int byte = window->bb->line_cur_pos;
       +        if (dir < 0) {
       +                byte--;
       +                while (byte > 0 &&
       +                       (window->bb->line_text[byte] & 0xC0) == 0x80) {
       +                        byte--;
       +                }
       +                if (byte < window->bb->min_pos)
       +                        byte = window->bb->min_pos;
       +                memmove(
       +                    window->bb->line_text + byte,
       +                    window->bb->line_text + window->bb->line_cur_pos,
       +                    window->bb->line_len - window->bb->line_cur_pos
       +                );
       +                window->bb->line_len -= (window->bb->line_cur_pos - byte);
       +                window->bb->line_cur_pos = byte;
       +        } else if (dir > 0) {
       +                byte++;
       +                while (byte < window->bb->line_len &&
       +                       (window->bb->line_text[byte] & 0xC0) == 0x80) {
       +                        byte++;
       +                }
       +                if (byte >= window->bb->line_len)
       +                        byte = window->bb->line_len;
       +                memmove(
       +                    window->bb->line_text + window->bb->line_cur_pos,
       +                    window->bb->line_text + byte,
       +                    window->bb->line_len - byte
       +                );
       +                window->bb->line_len -= (byte - window->bb->line_cur_pos);
       +        }
       +        /* FIXME: move to separate function */
       +        window->bb->line_text[window->bb->line_len] = '\0';
       +        pango_layout_set_text(window->bb->line, window->bb->line_text, window->bb->line_len);
       +        pango_layout_get_pixel_size(window->bb->line, &window->bb->line_w, &window->bb->line_h);
       +        ledit_draw_grow(window, window->bb->line_draw, window->bb->line_w, window->bb->line_h);
       +        XftDrawRect(window->bb->line_draw->xftdraw, &window->theme->text_bg, 0, 0, window->bb->line_w, window->bb->line_h);
       +        pango_xft_render_layout(window->bb->line_draw->xftdraw, &window->theme->text_fg, window->bb->line, 0, 0);
       +        recalc_text_size(window);
       +}
       +
       +void
        ledit_window_set_bottom_bar_cursor(ledit_window *window, int byte_pos) {
                /* FIXME: check if valid? */
                window->bb->line_cur_pos = byte_pos;
       t@@ -380,6 +466,7 @@ ledit_window_create(ledit_common *common, ledit_theme *theme) {
                window->bb->line_text = NULL;
                window->bb->line_alloc = window->bb->line_len = 0;
                window->bb->line_cur_pos = 0;
       +        window->bb->min_pos = 0;
                window->bottom_text_shown = 0;
                window->message_shown = 0;
        
       t@@ -473,8 +560,16 @@ ledit_window_redraw(ledit_window *window) {
                    0, window->text_h,
                    window->w, window->h - window->text_h
                );
       -        if (window->bottom_text_shown) {
       -                /* move input method position to cursor */
       +        if (window->message_shown) {
       +                XCopyArea(
       +                    window->common->dpy, window->bb->line_draw->pixmap,
       +                    window->drawable, window->gc,
       +                    0, 0, window->bb->line_w, window->bb->line_h,
       +                    0, window->text_h
       +                );
       +        } else if (window->bottom_text_shown) {
       +                XSetForeground(window->common->dpy, window->gc, t->text_fg.pixel);
       +                /* move input method position to cursor and draw cursor */
                        PangoRectangle strong, weak;
                        pango_layout_get_cursor_pos(
                            window->bb->line, window->bb->line_cur_pos, &strong, &weak
       t@@ -484,14 +579,28 @@ ledit_window_redraw(ledit_window *window) {
                           have to be moved out of the way anyways (fcitx just moves it
                           up a bit so it sort of works) */
                        xximspot(window, strong.x / PANGO_SCALE, window->h);
       -        }
       -        if (window->bottom_text_shown || window->message_shown) {
       +                int x = 0;
       +                int w = window->bb->line_w;
       +                int cur_x = strong.x / PANGO_SCALE;
       +                if (w > window->w) {
       +                        /* FIXME: try to keep some space on the edges */
       +                        x = (cur_x / window->w) * window->w;
       +                        w = window->w;
       +                        if (x + w > window->bb->line_w)
       +                                w = window->bb->line_w - x;
       +                }
                        XCopyArea(
                            window->common->dpy, window->bb->line_draw->pixmap,
                            window->drawable, window->gc,
       -                    0, 0, window->bb->line_w, window->bb->line_h,
       +                    x, 0, w, window->bb->line_h,
                            0, window->text_h
                        );
       +                XDrawLine(
       +                    window->common->dpy, window->drawable, window->gc,
       +                    cur_x - x, window->text_h + strong.y / PANGO_SCALE,
       +                    cur_x - x,
       +                    window->text_h + (strong.y + strong.height) / PANGO_SCALE
       +                );
                } else {
                        XCopyArea(
                            window->common->dpy, window->bb->mode_draw->pixmap,
   DIR diff --git a/window.h b/window.h
       t@@ -46,6 +46,11 @@ void ledit_window_cleanup(void);
        
        /* FIXME: this is a bit confusing because there's a difference between editable
           text shown and non-editable message shown */
       +void ledit_window_move_bottom_bar_cursor(ledit_window *window, int movement);
       +void ledit_window_delete_bottom_bar_char(ledit_window *window, int dir);
       +void ledit_window_bottom_bar_cursor_to_beginning(ledit_window *window);
       +void ledit_window_bottom_bar_cursor_to_end(ledit_window *window);
       +void ledit_window_set_bottom_bar_min_pos(ledit_window *window, int pos);
        void ledit_window_set_bottom_bar_text_shown(ledit_window *window, int shown);
        int ledit_window_bottom_bar_text_shown(ledit_window *window);
        void ledit_window_set_bottom_bar_cursor(ledit_window *window, int byte_pos);