URI: 
       tStart implementing substitution - 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 123a3087ad0bc4f772f0cd0a17caf95304092f87
   DIR parent 82723082181a863b7bc7c7cbbcc92da5c30caf6d
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Wed,  8 Dec 2021 20:56:21 +0100
       
       Start implementing substitution
       
       Diffstat:
         M buffer.c                            |      16 ++++++++++++++++
         M buffer.h                            |      10 ++++++++++
         M keys.c                              |       6 +++++-
         M keys_command.c                      |     121 ++++++++++++++++++++++++++++++-
         M search.c                            |       2 +-
         M view.c                              |      14 ++++++++++++++
         M view.h                              |      15 +++++++++++++++
       
       7 files changed, 181 insertions(+), 3 deletions(-)
       ---
   DIR diff --git a/buffer.c b/buffer.c
       t@@ -143,6 +143,22 @@ buffer_create(ledit_common *common) {
                return buffer;
        }
        
       +void
       +buffer_lock_all_views_except(ledit_buffer *buffer, ledit_view *view, char *lock_text) {
       +        for (size_t i = 0; i < buffer->views_num; i++) {
       +                if (buffer->views[i] != view) {
       +                        view_lock(buffer->views[i], lock_text);
       +                }
       +        }
       +}
       +
       +void
       +buffer_unlock_all_views(ledit_buffer *buffer) {
       +        for (size_t i = 0; i < buffer->views_num; i++) {
       +                view_unlock(buffer->views[i]);
       +        }
       +}
       +
        static void
        set_view_hard_line_text(ledit_buffer *buffer, ledit_view *view) {
                char *text = buffer->hard_line_based ? "|HL" : "|SL";
   DIR diff --git a/buffer.h b/buffer.h
       t@@ -49,6 +49,16 @@ struct ledit_buffer {
        ledit_buffer *buffer_create(ledit_common *common);
        
        /*
       + * Lock all views except the given view.
       + */
       +void buffer_lock_all_views_except(ledit_buffer *buffer, ledit_view *view, char *lock_text);
       +
       +/*
       + * Unlock all views.
       + */
       +void buffer_unlock_all_views(ledit_buffer *buffer);
       +
       +/*
         * Set the hard line mode of the buffer and update the
         * displayed mode in all views.
         */
   DIR diff --git a/keys.c b/keys.c
       t@@ -27,7 +27,11 @@ get_language_index(char *lang) {
        }
        
        /* FIXME: Does this break anything? */
       -static unsigned int importantmod = ShiftMask | ControlMask | Mod1Mask | Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask;
       +/*static unsigned int importantmod = ShiftMask | ControlMask | Mod1Mask | Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask;*/
       +/* FIXME: ShiftMask is currently masked away anyways, so it isn't really important */
       +/* FIXME: The Mod*Masks can be remapped, so it isn't really clear what is what */
       +/* most are disabled now to avoid issues with e.g. numlock */
       +static unsigned int importantmod = ShiftMask | ControlMask | Mod1Mask;
        #define XK_ANY_MOD    UINT_MAX
        
        int
   DIR diff --git a/keys_command.c b/keys_command.c
       t@@ -27,6 +27,18 @@
        #include "keys_command.h"
        #include "keys_command_config.h"
        
       +static char *last_search = NULL;
       +static char *last_replacement = NULL;
       +static int last_replacement_global = 0;
       +
       +static int
       +view_locked_error(ledit_view *view) {
       +        window_show_message(view->window, view->lock_text, -1);
       +        return 0;
       +}
       +
       +#define CHECK_VIEW_LOCKED if (view->lock_text) return view_locked_error(view)
       +
        /* FIXME: history for search and commands */
        
        static int create_view(ledit_view *view, char *cmd, size_t l1, size_t l2);
       t@@ -104,7 +116,107 @@ handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2) {
                (void)cmd;
                (void)l1;
                (void)l2;
       -        printf("substitute\n");
       +        CHECK_VIEW_LOCKED;
       +        cmd++; /* remove 's' at beginning */
       +        size_t len = strlen(cmd);
       +        if (len == 0) goto error;
       +        /* FIXME: utf8 */
       +        char sep = cmd[0];
       +        cmd++;
       +        char *next = strchr(cmd, sep);
       +        if (next == NULL) goto error;
       +        *next = '\0';
       +        next++;
       +        char *last = strchr(next, sep);
       +        if (last == NULL) goto error;
       +        *last = '\0';
       +        last++;
       +        int confirm = 0, global = 0;
       +        char *c = last;
       +        while (*c != '\0') {
       +                switch (*c) {
       +                case 'c':
       +                        confirm = 1;
       +                        break;
       +                case 'g':
       +                        global = 1;
       +                        break;
       +                default:
       +                        goto error;
       +                }
       +                c++;
       +        }
       +        free(last_search);
       +        free(last_replacement);
       +        last_search = ledit_strdup(cmd);
       +        last_replacement = ledit_strdup(next);
       +        last_replacement_global = global;
       +
       +        if (confirm) {
       +                buffer_lock_all_views_except(view->buffer, view, "Ongoing substitution in other view.");
       +                buffer_unlock_all_views(view->buffer);
       +        } else {
       +                int num = 0;
       +                int start_undo_group = 1;
       +                size_t slen = strlen(last_search);
       +                size_t rlen = strlen(last_replacement);
       +                txtbuf *buf = txtbuf_new(); /* FIXME: don't allocate new every time */
       +                view_wipe_line_cursor_attrs(view, view->cur_line);
       +                for (size_t i = l1 - 1; i < l2; i++) {
       +                        ledit_line *ll = buffer_get_line(view->buffer, i);
       +                        buffer_normalize_line(ll);
       +                        char *pos = strstr(ll->text, last_search);
       +                        while (pos != NULL) {
       +                                size_t index = (size_t)(pos - ll->text);
       +                                ledit_range cur_range, del_range;
       +                                cur_range.line1 = view->cur_line;
       +                                cur_range.byte1 = view->cur_line;
       +                                view_delete_range(
       +                                    view, DELETE_CHAR,
       +                                    i, index,
       +                                    i, index + slen,
       +                                    &view->cur_line, &view->cur_index,
       +                                    &del_range, buf
       +                                );
       +                                cur_range.line2 = view->cur_line;
       +                                cur_range.byte2 = view->cur_index;
       +                                undo_push_delete(
       +                                    view->buffer->undo, buf, del_range, cur_range, start_undo_group, view->mode
       +                                );
       +                                start_undo_group = 0;
       +                                txtbuf ins_buf = {.text = last_replacement, .len = rlen, .cap = rlen};
       +                                cur_range.line1 = view->cur_line;
       +                                cur_range.byte1 = view->cur_index;
       +                                del_range.line1 = i;
       +                                del_range.byte1 = index;
       +                                size_t cur_line, cur_index;
       +                                buffer_insert_text_with_newlines(
       +                                    view->buffer, i, index, last_replacement, rlen,
       +                                    &cur_line, &cur_index
       +                                );
       +                                cur_range.line2 = view->cur_line;
       +                                cur_range.byte2 = view->cur_index;
       +                                del_range.line2 = cur_line;
       +                                del_range.byte2 = cur_index;
       +                                undo_push_insert(
       +                                    view->buffer->undo, &ins_buf, del_range, cur_range, 0, view->mode
       +                                );
       +                                num++;
       +                                if (!global) break;
       +                                buffer_normalize_line(ll); /* just in case */
       +                                pos = strstr(ll->text + index + rlen, last_search);
       +                        }
       +                }
       +                /* FIXME: show number replaced */
       +                /* this doesn't need to be added to the undo stack since it's called on undo/redo anyways */
       +                view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view->cur_index);
       +                view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
       +                view_ensure_cursor_shown(view);
       +                txtbuf_destroy(buf);
       +        }
       +        return 0;
       +error:
       +        window_show_message(view->window, "Invalid command", -1);
                return 0;
        }
        
       t@@ -133,6 +245,7 @@ $ last line
        */
        
        /* FIXME: ACTUALLY USE LEN!!! */
       +/* FIXME: allow using marks and selection range here */
        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@@ -180,6 +293,7 @@ parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *lin
                                        l1 = 1;
                                        l2 = view->lines_num;
                                        *l1_valid = *l2_valid = 1;
       +                                c++;
                                        break;
                                } else {
                                        return 1;
       t@@ -204,6 +318,8 @@ parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *lin
                }
                if ((!*l1_valid || !*l2_valid) && !(s & START_RANGE))
                        return 1;
       +        if ((*l1_valid || *l2_valid) && (l1 == 0 || l2 == 0 || l1 > view->lines_num || l2 > view->lines_num))
       +                return 1; /* FIXME: better error messages */
                *cmd_ret = c;
                *line1_ret = l1;
                *line2_ret = l2;
       t@@ -221,6 +337,7 @@ handle_cmd(ledit_view *view, char *cmd, size_t len) {
                if (parse_range(view, cmd, len, &c, &l1, &l2, &l1_valid, &l2_valid))
                        return 0;
                int range_given = l1_valid && l2_valid;
       +        /* FIXME: mandatory range */
                for (size_t i = 0; i < LENGTH(cmds); i++) {
                        if (!strncmp(cmds[i].cmd, c, strlen(cmds[i].cmd)) &&
                            (!range_given || cmds[i].type == CMD_OPTIONAL_RANGE)) {
       t@@ -335,6 +452,7 @@ search_next(ledit_view *view) {
                view_wipe_line_cursor_attrs(view, view->cur_line);
                enum ledit_search_state ret = ledit_search_next(view, &view->cur_line, &view->cur_index);
                view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
       +        view_ensure_cursor_shown(view);
                if (ret != SEARCH_NORMAL)
                        window_show_message(view->window, search_state_to_str(ret), -1);
        }
       t@@ -344,6 +462,7 @@ search_prev(ledit_view *view) {
                view_wipe_line_cursor_attrs(view, view->cur_line);
                enum ledit_search_state ret = ledit_search_prev(view, &view->cur_line, &view->cur_index);
                view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
       +        view_ensure_cursor_shown(view);
                if (ret != SEARCH_NORMAL)
                        window_show_message(view->window, search_state_to_str(ret), -1);
        }
   DIR diff --git a/search.c b/search.c
       t@@ -19,7 +19,7 @@
        #include "search.h"
        
        /* FIXME: make sure only whole utf8 chars are matched */
       -char *last_search = NULL;
       +static char *last_search = NULL;
        enum {
                FORWARD,
                BACKWARD
   DIR diff --git a/view.c b/view.c
       t@@ -112,6 +112,7 @@ view_create(ledit_buffer *buffer, ledit_theme *theme, enum ledit_mode mode, size
                view->window = window_create(buffer->common, theme, mode);
                view->theme = theme;
                view->cache = cache_create(buffer->common->dpy);
       +        view->lock_text = NULL;
                view->cur_action = (struct action){ACTION_NONE, NULL};
                window_set_scroll_callback(view->window, &view_scroll_handler, view);
                window_set_button_callback(view->window, &view_button_handler, view);
       t@@ -147,6 +148,18 @@ view_create(ledit_buffer *buffer, ledit_theme *theme, enum ledit_mode mode, size
                return view;
        }
        
       +void
       +view_lock(ledit_view *view, char *lock_text) {
       +        free(view->lock_text);
       +        view->lock_text = ledit_strdup(lock_text);
       +}
       +
       +void
       +view_unlock(ledit_view *view) {
       +        free(view->lock_text);
       +        view->lock_text = NULL;
       +}
       +
        ledit_view_line *
        view_get_line(ledit_view *view, size_t index) {
                assert(index < view->lines_num);
       t@@ -262,6 +275,7 @@ void
        view_destroy(ledit_view *view) {
                cache_destroy(view->cache);
                window_destroy(view->window);
       +        free(view->lock_text);
                free(view->lines);
                free(view);
        }
   DIR diff --git a/view.h b/view.h
       t@@ -57,6 +57,7 @@ struct ledit_view {
                ledit_theme *theme;       /* current theme in use */
                ledit_cache *cache;       /* cache for pixmaps and pango layouts */
                ledit_view_line *lines;   /* array of lines, stored as gap buffer */
       +        char *lock_text;          /* text to show if view is locked, i.e. no edits allowed */
                /* current command type - used by key handler in keys_command.c */
                enum ledit_command_type cur_command_type;
                struct action cur_action; /* current action to execute on key press */
       t@@ -98,6 +99,20 @@ ledit_view *view_create(
        );
        
        /*
       + * Lock a view.
       + * Views are locked for instance when substitution with confirmation is
       + * being performed in another view to avoid an inconsistent state.
       + * This currently only sets the lock text - commands using the view need
       + * to make sure to check that it isn't locked.
       + */
       +void view_lock(ledit_view *view, char *text);
       +
       +/*
       + * Unlock a view.
       + */
       +void view_unlock(ledit_view *view);
       +
       +/*
         * Get the view line at the given index.
         */
        ledit_view_line *view_get_line(ledit_view *view, size_t index);