URI: 
       tImprove range parsing; add search and command history - 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 7501cea8774e0b58705da53357947723f213cbf5
   DIR parent 0498ed82f507017d8a5c6d83da08c66b2520bf95
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Thu,  9 Dec 2021 20:10:59 +0100
       
       Improve range parsing; add search and command history
       
       Diffstat:
         M keys_basic.c                        |       5 +++--
         M keys_command.c                      |     233 +++++++++++++++++++++++++++----
         M keys_command.h                      |       1 +
         M keys_command_config.h               |      10 ++++++++++
         M ledit.c                             |       1 +
         M search.c                            |       5 +++--
         M undo.c                              |       2 +-
         M util.c                              |       7 +++++++
         M util.h                              |       2 ++
         M window.c                            |      14 ++++++++++++++
         M window.h                            |      14 +++++++++++++-
       
       11 files changed, 262 insertions(+), 32 deletions(-)
       ---
   DIR diff --git a/keys_basic.c b/keys_basic.c
       t@@ -1839,9 +1839,10 @@ static struct action
        enter_commandedit(ledit_view *view, char *text, size_t len) {
                (void)text;
                (void)len;
       -        window_set_bottom_bar_text(view->window, ":", -1);
       +        char *str = view->sel_valid ? ":'<,'>" : ":";
       +        window_set_bottom_bar_text(view->window, str, -1);
       +        window_set_bottom_bar_cursor(view->window, strlen(str));
                window_set_bottom_bar_min_pos(view->window, 1);
       -        window_set_bottom_bar_cursor(view->window, 1);
                view->cur_command_type = CMD_EDIT;
                window_set_bottom_bar_text_shown(view->window, 1);
                discard_repetition_stack();
   DIR diff --git a/keys_command.c b/keys_command.c
       t@@ -22,6 +22,7 @@
        #include "view.h"
        #include "search.h"
        #include "cleanup.h"
       +#include "util.h"
        
        #include "keys.h"
        #include "keys_command.h"
       t@@ -42,6 +43,53 @@ static struct {
                int start_group; /* only set for the first replacement */
        } sub_state = {NULL, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
        
       +typedef struct {
       +        size_t len, cur, cap;
       +        char **cmds;
       +} history;
       +
       +history cmdhistory = {0, 0, 0, NULL};
       +
       +history searchhistory = {0, 0, 0, NULL};
       +
       +static void
       +push_history(history *hist, char *cmd, size_t len) {
       +        if (hist->len >= hist->cap) {
       +                size_t cap = hist->cap * 2 > hist->cap + 2 ? hist->cap * 2 : hist->cap + 2;
       +                if (cap <= hist->len)
       +                        exit(1); /* FIXME: overflow */
       +                hist->cmds = ledit_reallocarray(hist->cmds, cap, sizeof(char *));
       +                hist->cap = cap;
       +        }
       +        hist->cmds[hist->len] = ledit_strndup(cmd, len);
       +        hist->len++;
       +        hist->cur = hist->len;
       +}
       +
       +static void
       +push_cmdhistory(char *cmd, size_t len) {
       +        push_history(&cmdhistory, cmd, len);
       +}
       +
       +static void
       +push_searchhistory(char *search, size_t len) {
       +        push_history(&searchhistory, search, len);
       +}
       +
       +void
       +command_key_cleanup(void) {
       +        free(sub_state.search);
       +        free(sub_state.replace);
       +        for (size_t i = 0; i < cmdhistory.len; i++) {
       +                free(cmdhistory.cmds[i]);
       +        }
       +        for (size_t i = 0; i < searchhistory.len; i++) {
       +                free(searchhistory.cmds[i]);
       +        }
       +        free(cmdhistory.cmds);
       +        free(searchhistory.cmds);
       +}
       +
        static int
        view_locked_error(ledit_view *view) {
                window_show_message(view->window, view->lock_text, -1);
       t@@ -231,18 +279,20 @@ handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2) {
                CHECK_VIEW_LOCKED(view);
                cmd++; /* remove 's' at beginning */
                size_t len = strlen(cmd);
       +        char *sep = NULL;
                if (len == 0) goto error;
       -        /* FIXME: utf8 */
       -        char sep = cmd[0];
       -        cmd++;
       -        char *next = strchr(cmd, sep);
       +        char *sepend = next_utf8(cmd + 1);
       +        size_t seplen = sepend - cmd;
       +        sep = ledit_strndup(cmd, seplen);
       +        cmd += seplen;
       +        char *next = strstr(cmd, sep);
                if (next == NULL) goto error;
                *next = '\0';
       -        next++;
       -        char *last = strchr(next, sep);
       +        next += seplen;
       +        char *last = strstr(next, sep);
                if (last == NULL) goto error;
                *last = '\0';
       -        last++;
       +        last += seplen;
                int confirm = 0, global = 0;
                char *c = last;
                while (*c != '\0') {
       t@@ -280,9 +330,11 @@ handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2) {
                } else {
                        substitute_all_remaining(view);
                }
       +        free(sep);
                return 0;
        error:
                window_show_message(view->window, "Invalid command", -1);
       +        free(sep);
                return 0;
        }
        
       t@@ -305,13 +357,14 @@ static const struct {
        };
        
        /*
       -. current line - FIXME: implement
       +. current line
        $ last line
        % all lines
        */
        
        /* FIXME: ACTUALLY USE LEN!!! */
       -/* FIXME: allow using marks and selection range here */
       +/* NOTE: Marks are only recognized here if they are one unicode character! */
       +/* NOTE: Only the line range of the selection is used at the moment. */
        static int
        parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *line1_ret, size_t *line2_ret, int *l1_valid, int *l2_valid) {
                (void)len;
       t@@ -325,7 +378,7 @@ parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *lin
                *l1_valid = 0;
                *l2_valid = 0;
                char *c = cmd;
       -        for (; *c != '\0'; c++) {
       +        while (*c != '\0') {
                        if (isdigit(*c)) {
                                /* FIXME: integer overflow */
                                if (s & IN_LINENO) {
       t@@ -338,21 +391,43 @@ parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *lin
                                } else if ((s & START_LINENO) && (s & START_RANGE)) {
                                        l1 = *c - '0';
                                        *l1_valid = 1;
       -                                s &= ~START_RANGE;
       -                                s &= ~START_LINENO;
       -                                s |= IN_RANGE | IN_LINENO;
       +                                s = IN_RANGE | IN_LINENO;
                                } else if ((s & START_LINENO)) {
                                        l2 = *c - '0';
                                        *l2_valid = 1;
       -                                s &= ~START_LINENO;
       -                                s |= IN_LINENO;
       +                                s = IN_LINENO;
       +                        }
       +                } else if (*c == '\'' && (s & START_LINENO)) {
       +                        if (c[1] == '\0' || c[2] == '\0')
       +                                return 1;
       +                        char *aftermark = next_utf8(c + 2);
       +                        size_t marklen = aftermark - (c + 1);
       +                        size_t l, b;
       +                        if (!strncmp(c + 1, "<", strlen("<")) && view->sel_valid) {
       +                                l = view->sel.line1 < view->sel.line2 ? view->sel.line1 : view->sel.line2;
       +                        } else if (!strncmp(c + 1, ">", strlen(">")) && view->sel_valid) {
       +                                l = view->sel.line1 > view->sel.line2 ? view->sel.line1 : view->sel.line2;
       +                        } else {
       +                                if (buffer_get_mark(view->buffer, c + 1, marklen, &l, &b)) {
       +                                        /* FIXME: show better error message */
       +                                        return 1;
       +                                }
       +                        }
       +                        if (!*l1_valid) {
       +                                l1 = l + 1;
       +                                *l1_valid = 1;
       +                        } else {
       +                                l2 = l + 1;
       +                                *l2_valid = 1;
                                }
       +                        c = aftermark;
       +                        s = 0;
       +                        continue;
                        } else if (*c == ',' && !(s & START_RANGE)) {
                                if (*l1_valid && *l2_valid) {
                                        return 1;
                                } else {
       -                                s |= START_LINENO;
       -                                s &= ~IN_LINENO;
       +                                s = START_LINENO;
                                }
                        } else if (*c == '%') {
                                if (s & START_RANGE) {
       t@@ -367,20 +442,33 @@ parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *lin
                        } else if (*c == '$') {
                                if (s & START_LINENO) {
                                        if (!*l1_valid) {
       +                                        l1 = view->cur_line + 1;
       +                                        *l1_valid = 1;
       +                                } else {
       +                                        l2 = view->cur_line + 1;
       +                                        *l2_valid = 1;
       +                                }
       +                                s = 0;
       +                        } else {
       +                                return 1;
       +                        }
       +                } else if (*c == '$') {
       +                        if (s & START_LINENO) {
       +                                if (!*l1_valid) {
                                                l1 = view->lines_num;
                                                *l1_valid = 1;
                                        } else {
                                                l2 = view->lines_num;
                                                *l2_valid = 1;
                                        }
       -                                s &= ~START_LINENO;
       -                                s &= ~IN_LINENO;
       +                                s = 0;
                                } else {
                                        return 1;
                                }
                        } else {
                                break;
                        }
       +                c++;
                }
                if ((!*l1_valid || !*l2_valid) && !(s & START_RANGE))
                        return 1;
       t@@ -397,12 +485,14 @@ static int
        handle_cmd(ledit_view *view, char *cmd, size_t len) {
                if (len < 1)
                        return 0;
       +        push_cmdhistory(cmd, len);
                char *c;
                size_t l1, l2;
                int l1_valid, l2_valid;
       -        /* FIXME: show error msg here */
       -        if (parse_range(view, cmd, len, &c, &l1, &l2, &l1_valid, &l2_valid))
       +        if (parse_range(view, cmd, len, &c, &l1, &l2, &l1_valid, &l2_valid)) {
       +                window_show_message(view->window, "Error parsing command", -1);
                        return 0;
       +        }
                int range_given = l1_valid && l2_valid;
                if (!range_given) {
                        l1 = l2 = view->cur_line;
       t@@ -413,6 +503,7 @@ handle_cmd(ledit_view *view, char *cmd, size_t len) {
                                return cmds[i].handler(view, c, l1, l2);
                        }
                }
       +        window_show_message(view->window, "Invalid command", -1);
                return 0;
        }
        
       t@@ -531,8 +622,72 @@ edit_submit(ledit_view *view, char *key_text, size_t len) {
                (void)key_text;
                (void)len;
                window_set_bottom_bar_text_shown(view->window, 0);
       +        char *text = window_get_bottom_bar_text(view->window);
       +        int min_pos = window_get_bottom_bar_min_pos(view->window);
       +        int textlen = strlen(text);
       +        /* this should never happen */
       +        if (min_pos > textlen) {
       +                textlen = 0;
       +        } else {
       +                textlen -= min_pos;
       +                text += min_pos;
       +        }
                /* FIXME: this is hacky */
       -        return handle_cmd(view, window_get_bottom_bar_text(view->window) + 1, -1);
       +        return handle_cmd(view, text, (size_t)textlen);
       +}
       +
       +static int
       +edit_prevcommand(ledit_view *view, char *key_text, size_t len) {
       +        (void)key_text;
       +        (void)len;
       +        if (cmdhistory.cur > 0) {
       +                cmdhistory.cur--;
       +                window_set_bottom_bar_realtext(view->window, cmdhistory.cmds[cmdhistory.cur], -1);
       +                window_bottom_bar_cursor_to_end(view->window);
       +        }
       +        return 1;
       +}
       +
       +static int
       +edit_nextcommand(ledit_view *view, char *key_text, size_t len) {
       +        (void)key_text;
       +        (void)len;
       +        if (cmdhistory.len > 0 && cmdhistory.cur < cmdhistory.len - 1) {
       +                cmdhistory.cur++;
       +                window_set_bottom_bar_realtext(view->window, cmdhistory.cmds[cmdhistory.cur], -1);
       +        } else {
       +                cmdhistory.cur = cmdhistory.len;
       +                window_set_bottom_bar_realtext(view->window, "", -1);
       +        }
       +        window_bottom_bar_cursor_to_end(view->window);
       +        return 1;
       +}
       +
       +static int
       +edit_prevsearch(ledit_view *view, char *key_text, size_t len) {
       +        (void)key_text;
       +        (void)len;
       +        if (searchhistory.cur > 0) {
       +                searchhistory.cur--;
       +                window_set_bottom_bar_realtext(view->window, searchhistory.cmds[searchhistory.cur], -1);
       +                window_bottom_bar_cursor_to_end(view->window);
       +        }
       +        return 1;
       +}
       +
       +static int
       +edit_nextsearch(ledit_view *view, char *key_text, size_t len) {
       +        (void)key_text;
       +        (void)len;
       +        if (searchhistory.len > 0 && searchhistory.cur < searchhistory.len - 1) {
       +                searchhistory.cur++;
       +                window_set_bottom_bar_realtext(view->window, searchhistory.cmds[searchhistory.cur], -1);
       +        } else {
       +                searchhistory.cur = searchhistory.len;
       +                window_set_bottom_bar_realtext(view->window, "", -1);
       +        }
       +        window_bottom_bar_cursor_to_end(view->window);
       +        return 1;
        }
        
        /* FIXME: support visual mode, i.e. change selection to new place? */
       t@@ -561,8 +716,21 @@ editsearch_submit(ledit_view *view, char *key_text, size_t len) {
                (void)key_text;
                (void)len;
                window_set_bottom_bar_text_shown(view->window, 0);
       -        set_search_forward(window_get_bottom_bar_text(view->window) + 1);
       -        search_next(view);
       +        char *text = window_get_bottom_bar_text(view->window);
       +        int min_pos = window_get_bottom_bar_min_pos(view->window);
       +        int textlen = strlen(text);
       +        /* this should always be the case */
       +        if (min_pos <= textlen) {
       +                if (min_pos < textlen)
       +                        push_searchhistory(text + min_pos, textlen - min_pos);
       +                set_search_forward(text + min_pos);
       +                search_next(view);
       +        } else {
       +                window_show_message(
       +                    view->window,
       +                    "Error in program. Tell lumidify about it.", -1
       +                );
       +        }
                return 0;
        }
        
       t@@ -571,8 +739,21 @@ editsearchb_submit(ledit_view *view, char *key_text, size_t len) {
                (void)key_text;
                (void)len;
                window_set_bottom_bar_text_shown(view->window, 0);
       -        set_search_backward(window_get_bottom_bar_text(view->window) + 1);
       -        search_next(view);
       +        char *text = window_get_bottom_bar_text(view->window);
       +        int min_pos = window_get_bottom_bar_min_pos(view->window);
       +        int textlen = strlen(text);
       +        /* this should always be the case */
       +        if (min_pos <= textlen) {
       +                if (min_pos < textlen)
       +                        push_searchhistory(text + min_pos, textlen - min_pos);
       +                set_search_backward(text + min_pos);
       +                search_next(view);
       +        } else {
       +                window_show_message(
       +                    view->window,
       +                    "Error in program. Tell lumidify about it.", -1
       +                );
       +        }
                return 0;
        }
        
   DIR diff --git a/keys_command.h b/keys_command.h
       t@@ -2,4 +2,5 @@
        void search_next(ledit_view *view);
        void search_prev(ledit_view *view);
        
       +void command_key_cleanup(void);
        struct action command_key_handler(ledit_view *view, XEvent *event, int lang_index);
   DIR diff --git a/keys_command_config.h b/keys_command_config.h
       t@@ -16,6 +16,10 @@ static int edit_cursor_to_beginning(ledit_view *view, char *key_text, size_t len
        static int edit_backspace(ledit_view *view, char *key_text, size_t len);
        static int edit_delete(ledit_view *view, char *key_text, size_t len);
        static int edit_submit(ledit_view *view, char *key_text, size_t len);
       +static int edit_prevcommand(ledit_view *view, char *key_text, size_t len);
       +static int edit_nextcommand(ledit_view *view, char *key_text, size_t len);
       +static int edit_prevsearch(ledit_view *view, char *key_text, size_t len);
       +static int edit_nextsearch(ledit_view *view, char *key_text, size_t len);
        static int editsearch_submit(ledit_view *view, char *key_text, size_t len);
        static int editsearchb_submit(ledit_view *view, char *key_text, size_t len);
        static int edit_discard(ledit_view *view, char *key_text, size_t len);
       t@@ -43,6 +47,12 @@ static struct key keys_en[] = {
                {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_Up, CMD_EDIT, &edit_prevcommand},
       +        {NULL, 0, XK_Up, CMD_EDITSEARCH, &edit_prevsearch},
       +        {NULL, 0, XK_Up, CMD_EDITSEARCHB, &edit_prevsearch},
       +        {NULL, 0, XK_Down, CMD_EDIT, &edit_nextcommand},
       +        {NULL, 0, XK_Down, CMD_EDITSEARCH, &edit_nextsearch},
       +        {NULL, 0, XK_Down, CMD_EDITSEARCHB, &edit_nextsearch},
                {NULL, 0, XK_BackSpace, CMD_EDIT, &edit_backspace},
                {NULL, 0, XK_BackSpace, CMD_EDITSEARCH, &edit_backspace},
                {NULL, 0, XK_BackSpace, CMD_EDITSEARCHB, &edit_backspace},
   DIR diff --git a/ledit.c b/ledit.c
       t@@ -259,6 +259,7 @@ ledit_cleanup(void) {
                /* FIXME: check for other things to clean up */
                search_cleanup();
                basic_key_cleanup();
       +        command_key_cleanup();
                if (buffer)
                        buffer_destroy(buffer);
                if (theme)
   DIR diff --git a/search.c b/search.c
       t@@ -48,7 +48,8 @@ static enum ledit_search_state
        search_forward(ledit_view *view, size_t *line_ret, size_t *byte_ret) {
                *line_ret = view->cur_line;
                *byte_ret = view->cur_index;
       -        if (last_search == NULL)
       +        /* if last_search is empty, strstr will find the ending '\0' */
       +        if (last_search == NULL || last_search[0] == '\0')
                        return SEARCH_NO_PATTERN;
                size_t line = view->cur_line;
                /* start one byte later so it doesn't get stuck on a match
       t@@ -96,7 +97,7 @@ static enum ledit_search_state
        search_backward(ledit_view *view, size_t *line_ret, size_t *byte_ret) {
                *line_ret = view->cur_line;
                *byte_ret = view->cur_index;
       -        if (last_search == NULL)
       +        if (last_search == NULL || last_search[0] == '\0')
                        return SEARCH_NO_PATTERN;
                size_t line = view->cur_line;
                size_t byte = view->cur_index;
   DIR diff --git a/undo.c b/undo.c
       t@@ -72,7 +72,7 @@ push_undo_elem(undo_stack *undo) {
                if (undo->len > undo->cap) {
                        /* FIXME: wait, why is it size_t here already? */
                        size_t cap = undo->len * 2;
       -                undo->stack = ledit_realloc(undo->stack, cap * sizeof(undo_elem));
       +                undo->stack = ledit_reallocarray(undo->stack, cap, sizeof(undo_elem));
                        for (size_t i = undo->cap; i < cap; i++) {
                                undo->stack[i].text = NULL;
                        }
   DIR diff --git a/util.c b/util.c
       t@@ -48,3 +48,10 @@ draw_destroy(ledit_window *window, ledit_draw *draw) {
                XftDrawDestroy(draw->xftdraw);
                free(draw);
        }
       +
       +char *
       +next_utf8(char *str) {
       +        while ((*str & 0xC0) == 0x80)
       +                str++;
       +        return str;
       +}
   DIR diff --git a/util.h b/util.h
       t@@ -24,3 +24,5 @@ void draw_grow(ledit_window *window, ledit_draw *draw, int w, int h);
         * Destroy a draw.
         */
        void draw_destroy(ledit_window *window, ledit_draw *draw);
       +
       +char *next_utf8(char *str);
   DIR diff --git a/window.c b/window.c
       t@@ -165,6 +165,11 @@ window_set_bottom_bar_min_pos(ledit_window *window, int pos) {
                window->bb->min_pos = pos;
        }
        
       +int
       +window_get_bottom_bar_min_pos(ledit_window *window) {
       +        return window->bb->min_pos;
       +}
       +
        void
        window_bottom_bar_cursor_to_beginning(ledit_window *window) {
                window->bb->line_cur_pos = window->bb->min_pos;
       t@@ -254,6 +259,15 @@ window_set_bottom_bar_text(ledit_window *window, char *text, int len) {
                window->redraw = 1;
        }
        
       +void
       +window_set_bottom_bar_realtext(ledit_window *window, char *text, int len) {
       +        if (window->bb->min_pos <= window->bb->line_len)
       +                window->bb->line_len = window->bb->min_pos;
       +        window->bb->line_cur_pos = window->bb->line_len;
       +        window_insert_bottom_bar_text(window, text, len);
       +        window->redraw = 1;
       +}
       +
        char *
        window_get_bottom_bar_text(ledit_window *window) {
                return window->bb->line_text;
   DIR diff --git a/window.h b/window.h
       t@@ -116,13 +116,18 @@ void window_bottom_bar_cursor_to_beginning(ledit_window *window);
        void window_bottom_bar_cursor_to_end(ledit_window *window);
        
        /*
       - * Set the minimum byte  position of the cursor of the editable text to 'pos'.
       + * Set the minimum byte position of the cursor of the editable text to 'pos'.
         * This means that the cursor will not be allowed to go further left
         * than that position (used e.g. for the ':' in commands).
         */
        void window_set_bottom_bar_min_pos(ledit_window *window, int pos);
        
        /*
       + * Get the mininum position (see above).
       + */
       +int window_get_bottom_bar_min_pos(ledit_window *window);
       +
       +/*
         * Set whether the editable text is shown.
         */
        void window_set_bottom_bar_text_shown(ledit_window *window, int shown);
       t@@ -153,6 +158,13 @@ void window_insert_bottom_bar_text(ledit_window *window, char *text, int len);
        void window_set_bottom_bar_text(ledit_window *window, char *text, int len);
        
        /*
       + * Set the text after the minimum position.
       + * If the set minimum position is after the current length
       + * of the text in the bar, the text is just appended.
       + */
       +void window_set_bottom_bar_realtext(ledit_window *window, char *text, int len);
       +
       +/*
         * Get the text of the editable line.
         */
        char *window_get_bottom_bar_text(ledit_window *window);