ledit

Text editor (WIP)
git clone git://lumidify.org/ledit.git (fast, but not encrypted)
git clone https://lumidify.org/ledit.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ledit.git (over tor)
Log | Files | Refs | README | 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 }