URI: 
       keys_command.c - ledit - Text editor (WIP)
  HTML git clone git://lumidify.org/ledit.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/ledit.git (encrypted, but very slow)
  HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ledit.git (over tor)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       keys_command.c (33463B)
       ---
            1 /* FIXME: remove CHECK_VIEW_LOCKED when it is confirmed that the new system works properly */
            2 /* FIXME: Parse commands properly and allow combinations of commands */
            3 /* FIXME: properly parse commands - in particular, shown an error if there are extra
            4    characters on the line */
            5 #include <stdio.h>
            6 #include <ctype.h>
            7 #include <stdlib.h>
            8 #include <stdint.h>
            9 #include <unistd.h>
           10 
           11 #include <X11/Xlib.h>
           12 #include <X11/Xutil.h>
           13 #include <pango/pangoxft.h>
           14 #include <X11/extensions/Xdbe.h>
           15 #include <X11/keysym.h>
           16 #include <X11/XF86keysym.h>
           17 #include <X11/cursorfont.h>
           18 
           19 #include "memory.h"
           20 #include "common.h"
           21 #include "txtbuf.h"
           22 #include "undo.h"
           23 #include "cache.h"
           24 #include "window.h"
           25 #include "buffer.h"
           26 #include "view.h"
           27 #include "search.h"
           28 #include "cleanup.h"
           29 #include "util.h"
           30 
           31 #include "keys.h"
           32 #include "keys_command.h"
           33 #include "configparser.h"
           34 
           35 /*************************************************************************
           36  * Declarations for all functions that can be used in the configuration. *
           37  *************************************************************************/
           38 
           39 static int substitute_yes(ledit_view *view, char *key_text, size_t len, size_t lang_index);
           40 static int substitute_yes_all(ledit_view *view, char *key_text, size_t len, size_t lang_index);
           41 static int substitute_no(ledit_view *view, char *key_text, size_t len, size_t lang_index);
           42 static int substitute_no_all(ledit_view *view, char *key_text, size_t len, size_t lang_index);
           43 static int edit_insert_text(ledit_view *view, char *key_text, size_t len, size_t lang_index);
           44 static int edit_cursor_left(ledit_view *view, char *key_text, size_t len, size_t lang_index);
           45 static int edit_cursor_right(ledit_view *view, char *key_text, size_t len, size_t lang_index);
           46 static int edit_cursor_to_end(ledit_view *view, char *key_text, size_t len, size_t lang_index);
           47 static int edit_cursor_to_beginning(ledit_view *view, char *key_text, size_t len, size_t lang_index);
           48 static int edit_backspace(ledit_view *view, char *key_text, size_t len, size_t lang_index);
           49 static int edit_delete(ledit_view *view, char *key_text, size_t len, size_t lang_index);
           50 static int edit_submit(ledit_view *view, char *key_text, size_t len, size_t lang_index);
           51 static int edit_prevcommand(ledit_view *view, char *key_text, size_t len, size_t lang_index);
           52 static int edit_nextcommand(ledit_view *view, char *key_text, size_t len, size_t lang_index);
           53 static int edit_prevsearch(ledit_view *view, char *key_text, size_t len, size_t lang_index);
           54 static int edit_nextsearch(ledit_view *view, char *key_text, size_t len, size_t lang_index);
           55 static int editsearch_submit(ledit_view *view, char *key_text, size_t len, size_t lang_index);
           56 static int editsearchb_submit(ledit_view *view, char *key_text, size_t len, size_t lang_index);
           57 static int edit_discard(ledit_view *view, char *key_text, size_t len, size_t lang_index);
           58 
           59 static int create_view(ledit_view *view, char *cmd, size_t l1, size_t l2);
           60 static int close_view(ledit_view *view, char *cmd, size_t l1, size_t l2);
           61 static int handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2);
           62 static int handle_quit(ledit_view *view, char *cmd, size_t l1, size_t l2);
           63 static int handle_write_quit(ledit_view *view, char *cmd, size_t l1, size_t l2);
           64 static int handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2);
           65 
           66 /***********************************************
           67  * String-function mapping for config parsing. *
           68  ***********************************************/
           69 
           70 typedef enum {
           71         KEY_FLAG_NONE = 0,
           72         KEY_FLAG_JUMP_TO_CURSOR = 1,
           73         KEY_FLAG_LOCK_ALLOWED = 2
           74 } command_key_cb_flags;
           75 
           76 typedef enum {
           77         CMD_FLAG_NONE = 0,
           78         CMD_FLAG_OPTIONAL_RANGE = 1,
           79         CMD_FLAG_LOCK_ALLOWED = 2
           80 } command_cb_flags;
           81 
           82 typedef int (*command_key_cb_func)(ledit_view *, char *, size_t, size_t);
           83 struct command_key_cb {
           84         char *text;
           85         command_key_cb_func func;
           86         command_key_cb_flags flags;
           87         command_mode allowed_modes;
           88 };
           89 
           90 typedef int (*command_cb_func)(ledit_view *view, char *cmd, size_t l1, size_t l2);
           91 struct command_cb {
           92         char *text;
           93         command_cb_func func;
           94         command_cb_flags flags;
           95 };
           96 
           97 int
           98 command_key_cb_modemask_is_valid(command_key_cb *cb, command_mode modes) {
           99         return (~cb->allowed_modes & modes) == 0;
          100 }
          101 
          102 static command_key_cb command_key_cb_map[] = {
          103         {"edit-backspace", &edit_backspace, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
          104         {"edit-cursor-left", &edit_cursor_left, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
          105         {"edit-cursor-right", &edit_cursor_right, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
          106         {"edit-cursor-to-beginning", &edit_cursor_to_beginning, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
          107         {"edit-cursor-to-end", &edit_cursor_to_end, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
          108         {"edit-delete", &edit_delete, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
          109         {"edit-discard", &edit_discard, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
          110         {"edit-insert-text", &edit_insert_text, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
          111         {"edit-next-command", &edit_nextcommand, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT},
          112         {"edit-next-search", &edit_nextsearch, KEY_FLAG_LOCK_ALLOWED, CMD_EDITSEARCH|CMD_EDITSEARCHB},
          113         {"edit-previous-command", &edit_prevcommand, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT},
          114         {"edit-previous-search", &edit_prevsearch, KEY_FLAG_LOCK_ALLOWED, CMD_EDITSEARCH|CMD_EDITSEARCHB},
          115         {"edit-submit", &edit_submit, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT},
          116         {"edit-submit-backwards-search", &editsearchb_submit, KEY_FLAG_LOCK_ALLOWED, CMD_EDITSEARCHB},
          117         {"edit-submit-search", &editsearch_submit, KEY_FLAG_LOCK_ALLOWED, CMD_EDITSEARCH},
          118         {"substitute-no", &substitute_no, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, CMD_SUBSTITUTE},
          119         {"substitute-no-all", &substitute_no_all, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, CMD_SUBSTITUTE},
          120         {"substitute-yes", &substitute_yes, KEY_FLAG_JUMP_TO_CURSOR, CMD_SUBSTITUTE},
          121         {"substitute-yes-all", &substitute_yes_all, KEY_FLAG_JUMP_TO_CURSOR, CMD_SUBSTITUTE},
          122 };
          123 
          124 static command_cb command_cb_map[] = {
          125         {"close-view", &close_view, CMD_FLAG_LOCK_ALLOWED},
          126         {"create-view", &create_view, CMD_FLAG_LOCK_ALLOWED},
          127         {"quit", &handle_quit, CMD_FLAG_LOCK_ALLOWED},
          128         {"substitute", &handle_substitute, CMD_FLAG_OPTIONAL_RANGE},
          129         {"write", &handle_write, CMD_FLAG_OPTIONAL_RANGE|CMD_FLAG_LOCK_ALLOWED},
          130         {"write-quit", &handle_write_quit, CMD_FLAG_OPTIONAL_RANGE|CMD_FLAG_LOCK_ALLOWED},
          131 };
          132 
          133 GEN_CB_MAP_HELPERS(command_key_cb_map, command_key_cb, text)
          134 GEN_CB_MAP_HELPERS(command_cb_map, command_cb, text)
          135 
          136 /***************************************************
          137  * General global variables and utility functions. *
          138  ***************************************************/
          139 
          140 static struct {
          141         char *search;
          142         char *replace;
          143         size_t slen;
          144         size_t rlen;
          145         size_t line;
          146         size_t byte;
          147         size_t old_line;
          148         size_t old_byte;
          149         size_t max_line;
          150         int global;
          151         int num;
          152         int start_group; /* only set for the first replacement */
          153 } sub_state = {NULL, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
          154 
          155 typedef struct {
          156         size_t len, cur, cap;
          157         char **cmds;
          158 } history;
          159 
          160 history cmdhistory = {0, 0, 0, NULL};
          161 
          162 history searchhistory = {0, 0, 0, NULL};
          163 
          164 static void
          165 push_history(history *hist, char *cmd, size_t len) {
          166         if (hist->len >= hist->cap) {
          167                 size_t cap = ideal_array_size(hist->cap, add_sz(hist->cap, 1));
          168                 hist->cmds = ledit_reallocarray(hist->cmds, cap, sizeof(char *));
          169                 hist->cap = cap;
          170         }
          171         hist->cmds[hist->len] = ledit_strndup(cmd, len);
          172         hist->len++;
          173         hist->cur = hist->len;
          174 }
          175 
          176 static void
          177 push_cmdhistory(char *cmd, size_t len) {
          178         push_history(&cmdhistory, cmd, len);
          179 }
          180 
          181 static void
          182 push_searchhistory(char *search, size_t len) {
          183         push_history(&searchhistory, search, len);
          184 }
          185 
          186 void
          187 command_key_cleanup(void) {
          188         free(sub_state.search);
          189         free(sub_state.replace);
          190         for (size_t i = 0; i < cmdhistory.len; i++) {
          191                 free(cmdhistory.cmds[i]);
          192         }
          193         for (size_t i = 0; i < searchhistory.len; i++) {
          194                 free(searchhistory.cmds[i]);
          195         }
          196         free(cmdhistory.cmds);
          197         free(searchhistory.cmds);
          198 }
          199 
          200 static int
          201 view_locked_error(ledit_view *view) {
          202         window_show_message(view->window, view->lock_text, -1);
          203         return 0;
          204 }
          205 
          206 #define CHECK_VIEW_LOCKED(view) if (view->lock_text) return view_locked_error(view)
          207 
          208 /********************************************************************
          209  * Functions for handling commands typed in line editor (:w, etc.). *
          210  ********************************************************************/
          211 
          212 static int parse_range(
          213     ledit_view *view, char *cmd, size_t len, size_t *idx_ret,
          214     size_t *line1_ret, size_t *line2_ret, int *l1_valid, int *l2_valid,
          215     char **errstr_ret
          216 );
          217 static int handle_cmd(ledit_view *view, char *cmd, size_t len, size_t lang_index);
          218 
          219 /* FIXME: USE LEN EVERYWHERE INSTEAD OF RELYING ON cmd BEING NUL-TERMINATED */
          220 /* returns 1 on error, 0 otherwise */
          221 static int
          222 handle_write_base(ledit_view *view, char *cmd) {
          223         #if TEST
          224         /* disallow normal file writing in test mode so no
          225            file can accidentally be destroyed by fuzz testing */
          226         (void)view;
          227         (void)cmd;
          228         return 0;
          229         #else
          230         /* FIXME: allow writing only part of file */
          231         char *filename = view->buffer->filename;
          232         int stored = 1;
          233         int force = 0;
          234         if (*cmd == '!') {
          235                 force = 1;
          236                 cmd++;
          237         }
          238         /* FIXME: string parsing instead of just taking the rest of the line */
          239         if (cmd[0] == ' ' && cmd[1] != '\0') {
          240                 filename = cmd + 1;
          241                 stored = 0;
          242         }
          243         /* FIXME: file locks */
          244         char *errstr = NULL;
          245         if (filename) {
          246                 struct stat sb;
          247                 /* There technically is a race between checking stat and actually
          248                    trying to write the file, but I don't care at the moment. */
          249                 int ret = 0;
          250                 if (!(ret = stat(filename, &sb)) && !force && stored &&
          251                     (sb.st_mtim.tv_sec != view->buffer->file_mtime.tv_sec ||
          252                      sb.st_mtim.tv_nsec != view->buffer->file_mtime.tv_nsec)) {
          253                         window_show_message_fmt(
          254                             view->window,
          255                             "%s: file modification time changed; use ! to override",
          256                             filename
          257                         );
          258                         return 1;
          259                 /* FIXME: I guess the file can still exist if stat returns an error,
          260                    but the writing itself will probably fail then as well. */
          261                 } else if (!ret && !force && !stored) {
          262                         window_show_message_fmt(
          263                             view->window,
          264                             "%s: file exists; use ! to override",
          265                             filename
          266                         );
          267                         return 1;
          268                 } else if (buffer_write_to_filename(view->buffer, filename, &errstr)) {
          269                         window_show_message_fmt(view->window, "Error writing %s: %s", filename, errstr);
          270                         return 1;
          271                 } else {
          272                         /* FIXME: better message */
          273                         window_show_message_fmt(view->window, "Wrote file %s", filename);
          274                         /* update modification time */
          275                         if (stat(filename, &sb)) {
          276                                 /* FIXME: what should be done here? */
          277                         } else {
          278                                 view->buffer->file_mtime = sb.st_mtim;
          279                         }
          280                 }
          281         } else {
          282                 window_show_message(view->window, "No file name", -1);
          283                 return 1;
          284         }
          285         return 0;
          286         #endif
          287 }
          288 
          289 static int
          290 handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2) {
          291         (void)l1;
          292         (void)l2;
          293         handle_write_base(view, cmd);
          294         return 0;
          295 }
          296 
          297 static int
          298 handle_quit(ledit_view *view, char *cmd, size_t l1, size_t l2) {
          299         (void)l1;
          300         (void)l2;
          301         int force = 0;
          302         if (*cmd == '!')
          303                 force = 1;
          304         if (view->buffer->modified && !force) {
          305                 window_show_message(view->window, "File modified; write or use ! to override", -1);
          306         } else {
          307                 ledit_cleanup();
          308                 exit(0);
          309         }
          310         return 0;
          311 }
          312 
          313 static int
          314 create_view(ledit_view *view, char *cmd, size_t l1, size_t l2) {
          315         (void)cmd;
          316         (void)l1;
          317         (void)l2;
          318         buffer_add_view(view->buffer, view->mode, view->cur_line, view->cur_index, view->display_offset);
          319         return 0;
          320 }
          321 
          322 static int
          323 close_view(ledit_view *view, char *cmd, size_t l1, size_t l2) {
          324         (void)cmd;
          325         (void)l1;
          326         (void)l2;
          327         /* FIXME: This will lead to problems if I add something that
          328            requires access to the view after the command is handled. */
          329         int force = 0;
          330         if (*cmd == '!')
          331                 force = 1;
          332         ledit_buffer *buffer = view->buffer;
          333         if (buffer->views_num == 1 && buffer->modified && !force) {
          334                 window_show_message(view->window, "File modified; write or use ! to override", -1);
          335         } else {
          336                 view->destroy = 1;
          337         }
          338         return 0;
          339 }
          340 
          341 static int
          342 handle_write_quit(ledit_view *view, char *cmd, size_t l1, size_t l2) {
          343         (void)l1;
          344         (void)l2;
          345         if (handle_write_base(view, cmd))
          346                 return 0;
          347         ledit_cleanup();
          348         exit(0);
          349         return 0;
          350 }
          351 
          352 static void
          353 show_num_substituted(ledit_view *view) {
          354         window_show_message_fmt(view->window, "%d substitution(s)", sub_state.num);
          355 }
          356 
          357 /* returns 1 when match was found, 0 otherwise */
          358 static int
          359 next_replace_pos(
          360     ledit_view *view,
          361     size_t line, size_t byte, size_t max_line,
          362     size_t *line_ret, size_t *byte_ret) {
          363         size_t start_index = byte;
          364         for (size_t i = line; i <= max_line; i++) {
          365                 ledit_line *ll = buffer_get_line(view->buffer, i);
          366                 buffer_normalize_line(ll);
          367                 char *pos = strstr(ll->text + start_index, sub_state.search);
          368                 if (pos != NULL) {
          369                         *line_ret = i;
          370                         *byte_ret = (size_t)(pos - ll->text);
          371                         return 1;
          372                 }
          373                 start_index = 0;
          374         }
          375         return 0;
          376 }
          377 
          378 /* returns whether keys should continue being captured */
          379 static int
          380 move_to_next_substitution(ledit_view *view) {
          381         ledit_theme *theme = config_get_theme();
          382         if (view->mode == NORMAL)
          383                 view_wipe_line_cursor_attrs(view, view->cur_line);
          384         else if (view->mode == VISUAL)
          385                 view_wipe_selection(view);
          386         if (!next_replace_pos(view, sub_state.line, sub_state.byte, sub_state.max_line, &sub_state.line, &sub_state.byte)) {
          387                 /* FIXME: why are these set here? */
          388                 view->cur_line = sub_state.line;
          389                 view->cur_index = sub_state.byte;
          390                 if (view->mode == NORMAL) {
          391                         view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view->cur_index);
          392                         view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
          393                 } else if (view->mode == VISUAL) {
          394                         view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index);
          395                 }
          396                 window_show_message(view->window, "No more matches", -1);
          397                 buffer_unlock_all_views(view->buffer);
          398                 return 0;
          399         }
          400         if (theme->highlight_search && view->mode != VISUAL) {
          401                 view_wipe_line_cursor_attrs(view, view->cur_line);
          402                 view_set_mode(view, VISUAL);
          403         }
          404         view->cur_line = sub_state.line;
          405         view->cur_index = sub_state.byte;
          406         if (view->mode == NORMAL) {
          407                 view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
          408         } else if (view->mode == VISUAL && theme->highlight_search) {
          409                 view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index + sub_state.slen);
          410         }
          411         window_show_message(view->window, "Replace? (y/Y/n/N)", -1);
          412         view_ensure_cursor_shown(view);
          413         return 1;
          414 }
          415 
          416 /* WARNING: sub_state must be set properly! */
          417 static void
          418 substitute_single(ledit_view *view) {
          419         ledit_range cur_range;
          420         cur_range.line1 = sub_state.old_line;
          421         cur_range.byte1 = sub_state.old_byte;
          422         cur_range.line2 = sub_state.line;
          423         cur_range.byte2 = sub_state.byte;
          424         buffer_delete_with_undo_base(
          425             view->buffer, cur_range,
          426             sub_state.start_group, view->mode,
          427             sub_state.line, sub_state.byte,
          428             sub_state.line, sub_state.byte + sub_state.slen, NULL
          429         );
          430         sub_state.start_group = 0;
          431         cur_range.line1 = sub_state.line;
          432         cur_range.byte1 = sub_state.byte;
          433         buffer_insert_with_undo_base(
          434             view->buffer, cur_range, 0, 0, view->mode,
          435             sub_state.line, sub_state.byte,
          436             sub_state.replace, sub_state.rlen,
          437             NULL, NULL
          438         );
          439         sub_state.num++;
          440 }
          441 
          442 static void
          443 substitute_all_remaining(ledit_view *view) {
          444         if (view->mode == NORMAL)
          445                 view_wipe_line_cursor_attrs(view, view->cur_line);
          446         else if (view->mode == VISUAL)
          447                 view_wipe_selection(view);
          448         size_t min_line = SIZE_MAX;
          449         while (next_replace_pos(view, sub_state.line, sub_state.byte, sub_state.max_line, &sub_state.line, &sub_state.byte)) {
          450                 if (sub_state.line < min_line)
          451                         min_line = sub_state.line;
          452                 substitute_single(view);
          453                 view->cur_line = sub_state.old_line = sub_state.line;
          454                 view->cur_index = sub_state.old_byte = sub_state.byte;
          455                 if (!sub_state.global) {
          456                         sub_state.line++;
          457                         sub_state.byte = 0;
          458                 } else {
          459                         sub_state.byte += sub_state.rlen;
          460                 }
          461         }
          462         if (min_line < view->lines_num)
          463                 buffer_recalc_all_views_from_line(view->buffer, min_line);
          464         window_show_message_fmt(view->window, "Replaced %d occurrence(s)", sub_state.num);
          465         if (view->mode == NORMAL) {
          466                 /* this doesn't need to be added to the undo stack since it's called on undo/redo anyways */
          467                 view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view->cur_index);
          468                 view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
          469         } else if (view->mode == VISUAL) {
          470                 view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index);
          471         }
          472         view_ensure_cursor_shown(view);
          473         buffer_unlock_all_views(view->buffer);
          474 }
          475 
          476 static int
          477 handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2) {
          478         CHECK_VIEW_LOCKED(view);
          479         size_t len = strlen(cmd);
          480         char *sep = NULL;
          481         if (len == 0) goto error;
          482         char *sepend = next_utf8(cmd + 1);
          483         size_t seplen = sepend - cmd;
          484         sep = ledit_strndup(cmd, seplen);
          485         cmd += seplen;
          486         char *next = strstr(cmd, sep);
          487         if (next == NULL) goto error;
          488         *next = '\0';
          489         next += seplen;
          490         char *last = strstr(next, sep);
          491         if (last == NULL) goto error;
          492         *last = '\0';
          493         last += seplen;
          494         int confirm = 0, global = 0;
          495         char *c = last;
          496         while (*c != '\0') {
          497                 switch (*c) {
          498                 case 'c':
          499                         confirm = 1;
          500                         break;
          501                 case 'g':
          502                         global = 1;
          503                         break;
          504                 default:
          505                         goto error;
          506                 }
          507                 c++;
          508         }
          509         free(sep);
          510         sep = NULL;
          511         free(sub_state.search);
          512         free(sub_state.replace);
          513         sub_state.search = ledit_strdup(cmd);
          514         sub_state.replace = ledit_strdup(next);
          515         sub_state.slen = strlen(sub_state.search);
          516         sub_state.rlen = strlen(sub_state.replace);
          517         sub_state.global = global;
          518         sub_state.line = l1;
          519         sub_state.byte = 0;
          520         sub_state.old_line = view->cur_line;
          521         sub_state.old_byte = view->cur_index;
          522         sub_state.max_line = l2;
          523         sub_state.num = 0;
          524         sub_state.start_group = 1;
          525 
          526         if (confirm) {
          527                 buffer_lock_all_views_except(view->buffer, view, "Ongoing substitution in other view.");
          528                 view->cur_command_type = CMD_SUBSTITUTE;
          529                 return move_to_next_substitution(view);
          530         } else {
          531                 substitute_all_remaining(view);
          532         }
          533         return 0;
          534 error:
          535         window_show_message(view->window, "Invalid command", -1);
          536         free(sep);
          537         return 0;
          538 }
          539 
          540 enum cmd_type {
          541         CMD_NORMAL,
          542         CMD_OPTIONAL_RANGE
          543 };
          544 
          545 /*
          546 . current line
          547 $ last line
          548 % all lines
          549 */
          550 
          551 /* NOTE: Marks are only recognized here if they are one unicode character! */
          552 /* NOTE: Only the line range of the selection is used at the moment. */
          553 static int
          554 parse_range(
          555     ledit_view *view, char *cmd, size_t len, size_t *idx_ret,
          556     size_t *line1_ret, size_t *line2_ret, int *l1_valid, int *l2_valid,
          557     char **errstr_ret) {
          558         *errstr_ret = "";
          559         enum {
          560                 START_LINENO = 1,
          561                 START_RANGE = 2,
          562                 IN_RANGE = 4,
          563                 IN_LINENO = 8
          564         } s = START_LINENO | START_RANGE;
          565         size_t l1 = 0, l2 = 0;
          566         *l1_valid = 0;
          567         *l2_valid = 0;
          568         size_t cur = 0;
          569         char *c;
          570         while (cur < len) {
          571                 c = &cmd[cur];
          572                 if (isdigit(*c)) {
          573                         if (s & IN_LINENO) {
          574                                 size_t *final = &l2;
          575                                 if (!*l2_valid) {
          576                                         final = &l1;
          577                                         *l1_valid = 1;
          578                                 }
          579                                 if (SIZE_MAX / 10 < *final) {
          580                                         *errstr_ret = "Integer overflow in range";
          581                                         return 1;
          582                                 }
          583                                 *final *= 10;
          584                                 if (SIZE_MAX - (*c - '0') < *final) {
          585                                         *errstr_ret = "Integer overflow in range";
          586                                         return 1;
          587                                 }
          588                                 *final += (*c - '0');
          589                         } else if ((s & START_LINENO) && (s & START_RANGE)) {
          590                                 l1 = *c - '0';
          591                                 *l1_valid = 1;
          592                                 s = IN_RANGE | IN_LINENO;
          593                         } else if ((s & START_LINENO)) {
          594                                 l2 = *c - '0';
          595                                 *l2_valid = 1;
          596                                 s = IN_LINENO;
          597                         }
          598                 } else if (*c == '\'' && (s & START_LINENO)) {
          599                         if (len - cur <= 2) {
          600                                 *errstr_ret = "Invalid range";
          601                                 return 1;
          602                         }
          603                         size_t aftermark_idx = cur + 2 + next_utf8_len(c + 2, len - cur - 2);
          604                         size_t marklen = aftermark_idx - (cur + 1);
          605                         size_t l, b;
          606                         if (*(c + 1) == '<' && view->sel_valid) {
          607                                 l = view->sel.line1 < view->sel.line2 ? view->sel.line1 : view->sel.line2;
          608                         } else if (*(c + 1) == '>' && view->sel_valid) {
          609                                 l = view->sel.line1 > view->sel.line2 ? view->sel.line1 : view->sel.line2;
          610                         } else if (buffer_get_mark(view->buffer, c + 1, marklen, &l, &b)) {
          611                                 *errstr_ret = "Invalid mark";
          612                                 return 1;
          613                         }
          614                         if (!*l1_valid) {
          615                                 l1 = l + 1;
          616                                 *l1_valid = 1;
          617                         } else {
          618                                 l2 = l + 1;
          619                                 *l2_valid = 1;
          620                         }
          621                         cur = aftermark_idx;
          622                         s = 0;
          623                         continue;
          624                 } else if (*c == ',' && !(s & START_RANGE)) {
          625                         if (*l1_valid && *l2_valid) {
          626                                 *errstr_ret = "Invalid range";
          627                                 return 1;
          628                         } else {
          629                                 s = START_LINENO;
          630                         }
          631                 } else if (*c == '%') {
          632                         if (s & START_RANGE) {
          633                                 l1 = 1;
          634                                 l2 = view->lines_num;
          635                                 *l1_valid = *l2_valid = 1;
          636                                 cur++;
          637                                 s = 0;
          638                                 break;
          639                         } else {
          640                                 *errstr_ret = "Invalid range";
          641                                 return 1;
          642                         }
          643                 } else if (*c == '.') {
          644                         if (s & START_LINENO) {
          645                                 if (!*l1_valid) {
          646                                         l1 = view->cur_line + 1;
          647                                         *l1_valid = 1;
          648                                 } else {
          649                                         l2 = view->cur_line + 1;
          650                                         *l2_valid = 1;
          651                                 }
          652                                 s = 0;
          653                         } else {
          654                                 *errstr_ret = "Invalid range";
          655                                 return 1;
          656                         }
          657                 } else if (*c == '$') {
          658                         if (s & START_LINENO) {
          659                                 if (!*l1_valid) {
          660                                         l1 = view->lines_num;
          661                                         *l1_valid = 1;
          662                                 } else {
          663                                         l2 = view->lines_num;
          664                                         *l2_valid = 1;
          665                                 }
          666                                 s = 0;
          667                         } else {
          668                                 *errstr_ret = "Invalid range";
          669                                 return 1;
          670                         }
          671                 } else {
          672                         break;
          673                 }
          674                 cur++;
          675         }
          676         if ((!*l1_valid || !*l2_valid) && !(s & START_RANGE)) {
          677                 *errstr_ret = "Invalid range";
          678                 return 1;
          679         }
          680         if ((*l1_valid || *l2_valid) && (l1 == 0 || l2 == 0 || l1 > view->lines_num || l2 > view->lines_num)) {
          681                 *errstr_ret = "Invalid line number in range";
          682                 return 1;
          683         }
          684         *idx_ret = cur;
          685         /* ranges are given 1-indexed by user */
          686         *line1_ret = l1 - 1;
          687         *line2_ret = l2 - 1;
          688         return 0;
          689 }
          690 
          691 static int
          692 handle_cmd(ledit_view *view, char *cmd, size_t len, size_t lang_index) {
          693         if (len < 1)
          694                 return 0;
          695         push_cmdhistory(cmd, len);
          696         size_t l1, l2;
          697         int l1_valid, l2_valid;
          698         char *errstr;
          699         size_t start_idx;
          700         if (parse_range(view, cmd, len, &start_idx, &l1, &l2, &l1_valid, &l2_valid, &errstr)) {
          701                 window_show_message(view->window, errstr, -1);
          702                 return 0;
          703         }
          704         if (start_idx >= len) {
          705                 window_show_message(view->window, "Invalid command", -1);
          706                 return 0;
          707         }
          708         size_t rem_len = len - start_idx;
          709         char *cur_str = cmd + start_idx;
          710         int range_given = l1_valid && l2_valid;
          711         if (!range_given) {
          712                 l1 = l2 = view->cur_line;
          713         }
          714         command_array *cur_cmds = config_get_commands(lang_index);
          715         char *cmd_text;
          716         size_t text_len;
          717         for (size_t i = 0; i < cur_cmds->num_cmds; i++) {
          718                 cmd_text = cur_cmds->cmds[i].text;
          719                 text_len = strlen(cmd_text);
          720                 if (rem_len < text_len)
          721                         continue;
          722                 if (!strncmp(cmd_text, cur_str, text_len)) {
          723                         if (range_given && !(cur_cmds->cmds[i].cb->flags & CMD_OPTIONAL_RANGE)) {
          724                                 window_show_message(view->window, "Command does not take range", -1);
          725                                 return 0;
          726                         } else if (view->lock_text && !(cur_cmds->cmds[i].cb->flags & CMD_FLAG_LOCK_ALLOWED)) {
          727                                 window_show_message(view->window, view->lock_text, -1);
          728                                 return 0;
          729                         }
          730                         return cur_cmds->cmds[i].cb->func(view, cur_str + text_len, l1, l2);
          731                 }
          732         }
          733         window_show_message(view->window, "Invalid command", -1);
          734         return 0;
          735 }
          736 
          737 /***********************************
          738  * Functions called on keypresses. *
          739  ***********************************/
          740 
          741 static int
          742 substitute_yes(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
          743         (void)key_text; (void)len; (void)lang_index;
          744         substitute_single(view);
          745         buffer_recalc_line(view->buffer, sub_state.line);
          746         if (!sub_state.global) {
          747                 sub_state.line++;
          748                 sub_state.byte = 0;
          749         } else {
          750                 sub_state.byte += sub_state.rlen;
          751         }
          752         int ret = move_to_next_substitution(view);
          753         if (!ret)
          754                 show_num_substituted(view);
          755         return ret;
          756 }
          757 
          758 static int
          759 substitute_yes_all(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
          760         (void)key_text; (void)len; (void)lang_index;
          761         substitute_all_remaining(view);
          762         show_num_substituted(view);
          763         return 0;
          764 }
          765 
          766 static int
          767 substitute_no(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
          768         (void)key_text; (void)len; (void)lang_index;
          769         if (!sub_state.global) {
          770                 sub_state.line++;
          771                 sub_state.byte = 0;
          772         } else {
          773                 sub_state.byte += sub_state.slen;
          774         }
          775         int ret = move_to_next_substitution(view);
          776         if (!ret)
          777                 show_num_substituted(view);
          778         return ret;
          779 }
          780 
          781 static int
          782 substitute_no_all(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
          783         (void)key_text; (void)len; (void)lang_index;
          784         buffer_unlock_all_views(view->buffer);
          785         show_num_substituted(view);
          786         return 0;
          787 }
          788 
          789 static int
          790 edit_insert_text(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
          791         (void)lang_index;
          792         /* FIXME: overflow */
          793         window_insert_bottom_bar_text(view->window, key_text, len);
          794         window_set_bottom_bar_cursor(
          795             view->window, ledit_window_get_bottom_bar_cursor(view->window) + len
          796         );
          797         return 1;
          798 }
          799 
          800 static int
          801 edit_cursor_to_end(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
          802         (void)key_text; (void)len; (void)lang_index;
          803         window_bottom_bar_cursor_to_end(view->window);
          804         return 1;
          805 }
          806 
          807 static int
          808 edit_cursor_to_beginning(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
          809         (void)key_text; (void)len; (void)lang_index;
          810         window_bottom_bar_cursor_to_beginning(view->window);
          811         return 1;
          812 }
          813 
          814 static int
          815 edit_cursor_left(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
          816         (void)key_text; (void)len; (void)lang_index;
          817         window_move_bottom_bar_cursor(view->window, -1);
          818         return 1;
          819 }
          820 
          821 static int
          822 edit_cursor_right(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
          823         (void)key_text; (void)len; (void)lang_index;
          824         window_move_bottom_bar_cursor(view->window, 1);
          825         return 1;
          826 }
          827 
          828 static int
          829 edit_backspace(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
          830         (void)key_text; (void)len; (void)lang_index;
          831         window_delete_bottom_bar_char(view->window, -1);
          832         return 1;
          833 }
          834 
          835 static int
          836 edit_delete(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
          837         (void)key_text; (void)len; (void)lang_index;
          838         window_delete_bottom_bar_char(view->window, 1);
          839         return 1;
          840 }
          841 
          842 static int
          843 edit_submit(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
          844         (void)key_text; (void)len;
          845         window_set_bottom_bar_text_shown(view->window, 0);
          846         char *text = window_get_bottom_bar_text(view->window);
          847         int min_pos = window_get_bottom_bar_min_pos(view->window);
          848         int textlen = strlen(text);
          849         /* this should never happen */
          850         if (min_pos > textlen) {
          851                 textlen = 0;
          852         } else {
          853                 textlen -= min_pos;
          854                 text += min_pos;
          855         }
          856         /* FIXME: this is hacky */
          857         char *cmd = ledit_strndup(text, textlen);
          858         int ret = handle_cmd(view, cmd, (size_t)textlen, lang_index);
          859         free(cmd);
          860         return ret;
          861 }
          862 
          863 static int
          864 edit_prevcommand(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
          865         (void)key_text; (void)len; (void)lang_index;
          866         if (cmdhistory.cur > 0) {
          867                 cmdhistory.cur--;
          868                 window_set_bottom_bar_realtext(view->window, cmdhistory.cmds[cmdhistory.cur], -1);
          869                 window_bottom_bar_cursor_to_end(view->window);
          870         }
          871         return 1;
          872 }
          873 
          874 static int
          875 edit_nextcommand(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
          876         (void)key_text; (void)len; (void)lang_index;
          877         if (cmdhistory.len > 0 && cmdhistory.cur < cmdhistory.len - 1) {
          878                 cmdhistory.cur++;
          879                 window_set_bottom_bar_realtext(view->window, cmdhistory.cmds[cmdhistory.cur], -1);
          880         } else {
          881                 cmdhistory.cur = cmdhistory.len;
          882                 window_set_bottom_bar_realtext(view->window, "", -1);
          883         }
          884         window_bottom_bar_cursor_to_end(view->window);
          885         return 1;
          886 }
          887 
          888 static int
          889 edit_prevsearch(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
          890         (void)key_text; (void)len; (void)lang_index;
          891         if (searchhistory.cur > 0) {
          892                 searchhistory.cur--;
          893                 window_set_bottom_bar_realtext(view->window, searchhistory.cmds[searchhistory.cur], -1);
          894                 window_bottom_bar_cursor_to_end(view->window);
          895         }
          896         return 1;
          897 }
          898 
          899 static int
          900 edit_nextsearch(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
          901         (void)key_text; (void)len; (void)lang_index;
          902         if (searchhistory.len > 0 && searchhistory.cur < searchhistory.len - 1) {
          903                 searchhistory.cur++;
          904                 window_set_bottom_bar_realtext(view->window, searchhistory.cmds[searchhistory.cur], -1);
          905         } else {
          906                 searchhistory.cur = searchhistory.len;
          907                 window_set_bottom_bar_realtext(view->window, "", -1);
          908         }
          909         window_bottom_bar_cursor_to_end(view->window);
          910         return 1;
          911 }
          912 
          913 /* FIXME: the current "highlight_search" support is a bit weird and will probably conflict
          914    in some way if other support for visual mode (e.g. only search in selection) is added */
          915 /* FIXME: support visual mode, i.e. change selection to new place? */
          916 /* FIXME: maybe have separate setting to allow highlighting search just when in visual
          917    mode (i.e. don't switch to visual mode automatically) */
          918 void
          919 search_next(ledit_view *view) {
          920         view_wipe_line_cursor_attrs(view, view->cur_line);
          921         size_t len = 0;
          922         search_state ret = ledit_search_next(view, &view->cur_line, &view->cur_index, &len);
          923         ledit_theme *theme = config_get_theme();
          924         /* FIXME: figure out key stack handling when modes are also changed here */
          925         if (theme->highlight_search && len > 0 && (ret == SEARCH_NORMAL || ret == SEARCH_WRAPPED)) {
          926                 view_set_mode(view, VISUAL);
          927                 view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index + len);
          928         } else if (view->mode == VISUAL) {
          929                 view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index);
          930         } else if (view->mode == NORMAL) {
          931                 view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
          932         }
          933         view_ensure_cursor_shown(view);
          934         if (ret != SEARCH_NORMAL)
          935                 window_show_message(view->window, search_state_to_str(ret), -1);
          936 }
          937 
          938 void
          939 search_prev(ledit_view *view) {
          940         view_wipe_line_cursor_attrs(view, view->cur_line);
          941         size_t len = 0;
          942         search_state ret = ledit_search_prev(view, &view->cur_line, &view->cur_index, &len);
          943         ledit_theme *theme = config_get_theme();
          944         if (theme->highlight_search && len > 0 && (ret == SEARCH_NORMAL || ret == SEARCH_WRAPPED)) {
          945                 view_set_mode(view, VISUAL);
          946                 view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index + len);
          947         } else if (view->mode == VISUAL) {
          948                 view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index);
          949         } if (view->mode == NORMAL) {
          950                 view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
          951         }
          952         view_ensure_cursor_shown(view);
          953         if (ret != SEARCH_NORMAL)
          954                 window_show_message(view->window, search_state_to_str(ret), -1);
          955 }
          956 
          957 static int
          958 editsearch_submit(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
          959         (void)key_text; (void)len; (void)lang_index;
          960         window_set_bottom_bar_text_shown(view->window, 0);
          961         char *text = window_get_bottom_bar_text(view->window);
          962         int min_pos = window_get_bottom_bar_min_pos(view->window);
          963         int textlen = strlen(text);
          964         /* this should always be the case */
          965         if (min_pos <= textlen) {
          966                 if (min_pos < textlen)
          967                         push_searchhistory(text + min_pos, textlen - min_pos);
          968                 set_search_forward(text + min_pos);
          969                 search_next(view);
          970         } else {
          971                 window_show_message(
          972                     view->window,
          973                     "Error in program. Tell lumidify about it.", -1
          974                 );
          975         }
          976         return 0;
          977 }
          978 
          979 static int
          980 editsearchb_submit(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
          981         (void)key_text; (void)len; (void)lang_index;
          982         window_set_bottom_bar_text_shown(view->window, 0);
          983         char *text = window_get_bottom_bar_text(view->window);
          984         int min_pos = window_get_bottom_bar_min_pos(view->window);
          985         int textlen = strlen(text);
          986         /* this should always be the case */
          987         if (min_pos <= textlen) {
          988                 if (min_pos < textlen)
          989                         push_searchhistory(text + min_pos, textlen - min_pos);
          990                 set_search_backward(text + min_pos);
          991                 search_next(view);
          992         } else {
          993                 window_show_message(
          994                     view->window,
          995                     "Error in program. Tell lumidify about it.", -1
          996                 );
          997         }
          998         return 0;
          999 }
         1000 
         1001 static int
         1002 edit_discard(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
         1003         (void)view; (void)key_text; (void)lang_index;
         1004         (void)len;
         1005         window_set_bottom_bar_text_shown(view->window, 0);
         1006         return 0;
         1007 }
         1008 
         1009 struct action
         1010 command_key_handler(ledit_view *view, unsigned int key_state, KeySym sym, char *buf, int n, int lang_index) {
         1011         command_key_array *cur_keys = config_get_command_keys(lang_index);
         1012         size_t num_keys = cur_keys->num_keys;
         1013         int grabkey = 1, found = 0;
         1014         command_key_cb_flags flags = KEY_FLAG_NONE;
         1015         for (size_t i = 0; i < num_keys; i++) {
         1016                 if (cur_keys->keys[i].text) {
         1017                         if (n > 0 &&
         1018                             (cur_keys->keys[i].modes & view->cur_command_type) &&
         1019                             ((!strncmp(cur_keys->keys[i].text, buf, n) &&
         1020                               match_key(cur_keys->keys[i].mods, key_state & ~ShiftMask)) ||
         1021                              cur_keys->keys[i].text[0] == '\0')) {
         1022                                 flags = cur_keys->keys[i].cb->flags;
         1023                                 if (!(flags & KEY_FLAG_LOCK_ALLOWED) && view->lock_text) {
         1024                                         (void)view_locked_error(view);
         1025                                         grabkey = 0;
         1026                                         break;
         1027                                 }
         1028                                 grabkey = cur_keys->keys[i].cb->func(view, buf, (size_t)n, lang_index);
         1029                                 found = 1;
         1030                                 break;
         1031                         }
         1032                 } else if ((cur_keys->keys[i].modes & view->cur_command_type) &&
         1033                            (cur_keys->keys[i].keysym == sym) &&
         1034                            (match_key(cur_keys->keys[i].mods, key_state))) {
         1035                         flags = cur_keys->keys[i].cb->flags;
         1036                         if (!(flags & KEY_FLAG_LOCK_ALLOWED) && view->lock_text) {
         1037                                 (void)view_locked_error(view);
         1038                                 grabkey = 0;
         1039                                 break;
         1040                         }
         1041                         grabkey = cur_keys->keys[i].cb->func(view, buf, (size_t)n, lang_index);
         1042                         found = 1;
         1043                         break;
         1044                 }
         1045         }
         1046         if (found && (flags & KEY_FLAG_JUMP_TO_CURSOR))
         1047                 view_ensure_cursor_shown(view);
         1048         /* FIXME: proper error on invalid key */
         1049         if (grabkey)
         1050                 return (struct action){ACTION_GRABKEY, &command_key_handler};
         1051         else
         1052                 return (struct action){ACTION_NONE, NULL};
         1053 }