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_basic.c (104229B)


      1 /* FIXME: all movement commands that modify the selection should first check if the selection is valid! */
      2 /* FIXME: should motion callbacks be ignored in visual mode as they currently are? */
      3 /* FIXME: check allowed modes at beginning of functions */
      4 /* FIXME: the stacks here are shared for all views which can cause weird
      5    behavior, but I'm not sure what would be more logical */
      6 /* FIXME: cursor isn't shown properly on spaces at end of softlines */
      7 /* FIXME: selection is sometimes not reset when it is "clicked away" */
      8 /* FIXME: use weak cursor */
      9 /* FIXME: spaces at end of soft line are weird in bidi text
     10    -> space is hidden when e.g. ltr text left and rtl text on right is wrapped */
     11 /* FIXME: some weird things still happen with selections staying as "ghosts"
     12    and being deleted at some later time even though they're not shown anymore */
     13 /* FIXME: delete everything concerned with selections in insert mode since
     14    they are now not allowed at all */
     15 /* FIXME: a lot of error checking in the individual functions may be redundant
     16    now that more checking is done beforehand for the allowed keys */
     17 /* FIXME: sort functions a bit better, maybe split file */
     18 /* FIXME: documentation */
     19 #include <stdio.h>
     20 #include <ctype.h>
     21 #include <stdlib.h>
     22 
     23 #include <X11/Xlib.h>
     24 #include <X11/Xutil.h>
     25 #include <pango/pangoxft.h>
     26 #include <X11/extensions/Xdbe.h>
     27 #include <X11/keysym.h>
     28 #include <X11/XF86keysym.h>
     29 #include <X11/cursorfont.h>
     30 
     31 #if ENABLE_UTF8PROC
     32 #include "utf8proc.h"
     33 #endif
     34 
     35 #include "util.h"
     36 #include "assert.h"
     37 #include "memory.h"
     38 #include "common.h"
     39 #include "txtbuf.h"
     40 #include "undo.h"
     41 #include "cache.h"
     42 #include "window.h"
     43 #include "buffer.h"
     44 #include "view.h"
     45 #include "search.h"
     46 
     47 #include "keys.h"
     48 #include "keys_basic.h"
     49 #include "keys_command.h"
     50 #include "configparser.h"
     51 
     52 /*************************************************************************
     53  * Declarations for all functions that can be used in the configuration. *
     54  *************************************************************************/
     55 
     56 static struct action cursor_left(ledit_view *view, char *text, size_t len);
     57 static struct action cursor_right(ledit_view *view, char *text, size_t len);
     58 static struct action cursor_up(ledit_view *view, char *text, size_t len);
     59 static struct action cursor_down(ledit_view *view, char *text, size_t len);
     60 static struct action break_line(ledit_view *view, char *text, size_t len);
     61 static struct action return_to_normal(ledit_view *view, char *text, size_t len);
     62 static struct action enter_insert(ledit_view *view, char *text, size_t len);
     63 static struct action cursor_to_beginning(ledit_view *view, char *text, size_t len);
     64 static struct action key_0(ledit_view *view, char *text, size_t len);
     65 static struct action push_0(ledit_view *view, char *text, size_t len);
     66 static struct action push_1(ledit_view *view, char *text, size_t len);
     67 static struct action push_2(ledit_view *view, char *text, size_t len);
     68 static struct action push_3(ledit_view *view, char *text, size_t len);
     69 static struct action push_4(ledit_view *view, char *text, size_t len);
     70 static struct action push_5(ledit_view *view, char *text, size_t len);
     71 static struct action push_6(ledit_view *view, char *text, size_t len);
     72 static struct action push_7(ledit_view *view, char *text, size_t len);
     73 static struct action push_8(ledit_view *view, char *text, size_t len);
     74 static struct action push_9(ledit_view *view, char *text, size_t len);
     75 static struct action delete(ledit_view *view, char *text, size_t len);
     76 static struct action enter_visual(ledit_view *view, char *text, size_t len);
     77 static struct action switch_selection_end(ledit_view *view, char *text, size_t len);
     78 static struct action clipcopy(ledit_view *view, char *text, size_t len);
     79 static struct action clippaste(ledit_view *view, char *text, size_t len);
     80 static struct action show_line(ledit_view *view, char *text, size_t len);
     81 static struct action enter_commandedit(ledit_view *view, char *text, size_t len);
     82 static struct action enter_searchedit_backward(ledit_view *view, char *text, size_t len);
     83 static struct action enter_searchedit_forward(ledit_view *view, char *text, size_t len);
     84 static struct action key_search_next(ledit_view *view, char *text, size_t len);
     85 static struct action key_search_prev(ledit_view *view, char *text, size_t len);
     86 static struct action undo(ledit_view *view, char *text, size_t len);
     87 static struct action redo(ledit_view *view, char *text, size_t len);
     88 static struct action insert_mode_insert_text(ledit_view *view, char *text, size_t len);
     89 static struct action repeat_command(ledit_view *view, char *text, size_t len);
     90 static struct action screen_up(ledit_view *view, char *text, size_t len);
     91 static struct action screen_down(ledit_view *view, char *text, size_t len);
     92 static struct action scroll_with_cursor_up(ledit_view *view, char *text, size_t len);
     93 static struct action scroll_with_cursor_down(ledit_view *view, char *text, size_t len);
     94 static struct action scroll_lines_up(ledit_view *view, char *text, size_t len);
     95 static struct action scroll_lines_down(ledit_view *view, char *text, size_t len);
     96 static struct action move_to_line(ledit_view *view, char *text, size_t len);
     97 static struct action paste_normal(ledit_view *view, char *text, size_t len);
     98 static struct action paste_normal_backwards(ledit_view *view, char *text, size_t len);
     99 static struct action change(ledit_view *view, char *text, size_t len);
    100 static struct action move_to_eol(ledit_view *view, char *text, size_t len);
    101 static struct action insert_mark(ledit_view *view, char *text, size_t len);
    102 static struct action jump_to_mark(ledit_view *view, char *text, size_t len);
    103 static struct action next_word(ledit_view *view, char *text, size_t len);
    104 static struct action next_word_end(ledit_view *view, char *text, size_t len);
    105 static struct action next_bigword(ledit_view *view, char *text, size_t len);
    106 static struct action next_bigword_end(ledit_view *view, char *text, size_t len);
    107 static struct action prev_word(ledit_view *view, char *text, size_t len);
    108 static struct action prev_bigword(ledit_view *view, char *text, size_t len);
    109 static struct action append_after_eol(ledit_view *view, char *text, size_t len);
    110 static struct action append_after_cursor(ledit_view *view, char *text, size_t len);
    111 static struct action append_line_above(ledit_view *view, char *text, size_t len);
    112 static struct action append_line_below(ledit_view *view, char *text, size_t len);
    113 static struct action find_next_char_forwards(ledit_view *view, char *text, size_t len);
    114 static struct action find_next_char_backwards(ledit_view *view, char *text, size_t len);
    115 static struct action find_char_forwards(ledit_view *view, char *text, size_t len);
    116 static struct action find_char_backwards(ledit_view *view, char *text, size_t len);
    117 static struct action change_to_eol(ledit_view *view, char *text, size_t len);
    118 static struct action delete_to_eol(ledit_view *view, char *text, size_t len);
    119 static struct action delete_chars_forwards(ledit_view *view, char *text, size_t len);
    120 static struct action delete_chars_backwards(ledit_view *view, char *text, size_t len);
    121 static struct action delete_chars_forwards_multiline(ledit_view *view, char *text, size_t len);
    122 static struct action delete_chars_backwards_multiline(ledit_view *view, char *text, size_t len);
    123 static struct action delete_graphemes_forwards(ledit_view *view, char *text, size_t len);
    124 static struct action delete_graphemes_backwards(ledit_view *view, char *text, size_t len);
    125 static struct action delete_graphemes_forwards_multiline(ledit_view *view, char *text, size_t len);
    126 static struct action delete_graphemes_backwards_multiline(ledit_view *view, char *text, size_t len);
    127 static struct action yank(ledit_view *view, char *text, size_t len);
    128 static struct action yank_lines(ledit_view *view, char *text, size_t len);
    129 static struct action uppercase(ledit_view *view, char *text, size_t len);
    130 static struct action lowercase(ledit_view *view, char *text, size_t len);
    131 static struct action replace(ledit_view *view, char *text, size_t len);
    132 static struct action cursor_to_first_non_ws(ledit_view *view, char *text, size_t len);
    133 static struct action join_lines(ledit_view *view, char *text, size_t len);
    134 static struct action insert_at_beginning(ledit_view *view, char *text, size_t len);
    135 static struct action toggle_hard_line_based(ledit_view *view, char *text, size_t len);
    136 
    137 /***********************************************
    138  * String-function mapping for config parsing. *
    139  ***********************************************/
    140 
    141 /* FIXME: delete-backwards, delete-forwards should be renamed;
    142    *key functions should be renamed (they're very vague) */
    143 
    144 typedef enum {
    145 	KEY_FLAG_NONE = 0,
    146 	KEY_FLAG_JUMP_TO_CURSOR = 1,
    147 	KEY_FLAG_LOCK_ALLOWED = 2
    148 } basic_key_cb_flags;
    149 
    150 typedef struct action (*basic_key_cb_func)(ledit_view *, char *, size_t);
    151 
    152 struct basic_key_cb {
    153 	char *text;
    154 	basic_key_cb_func func;
    155 	basic_key_cb_flags flags;
    156 	ledit_mode allowed_modes;
    157 };
    158 
    159 int
    160 basic_key_cb_modemask_is_valid(basic_key_cb *cb, ledit_mode modes) {
    161 	return (~cb->allowed_modes & modes) == 0;
    162 }
    163 
    164 /* FIXME: make functions work in more modes (e.g. cursor-to-first-non-whitespace) */
    165 static struct basic_key_cb basic_key_cb_map[] = {
    166 	{"append-after-cursor", &append_after_cursor, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL|INSERT},
    167 	{"append-after-eol", &append_after_eol, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL|INSERT},
    168 	{"append-line-above", &append_line_above, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL|INSERT},
    169 	{"append-line-below", &append_line_below, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL|INSERT},
    170 	{"break-line", &break_line, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
    171 	{"change", &change, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL|INSERT},
    172 	{"change-to-eol", &change_to_eol, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
    173 	{"clipboard-copy", &clipcopy, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    174 	{"clipboard-paste", &clippaste, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL|INSERT},
    175 	{"cursor-down", &cursor_down, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, VISUAL|INSERT|NORMAL},
    176 	{"cursor-left", &cursor_left, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, VISUAL|INSERT|NORMAL},
    177 	{"cursor-right", &cursor_right, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, VISUAL|INSERT|NORMAL},
    178 	{"cursor-to-beginning", &cursor_to_beginning, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    179 	{"cursor-to-first-non-whitespace", &cursor_to_first_non_ws, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    180 	{"cursor-up", &cursor_up, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, VISUAL|INSERT|NORMAL},
    181 	{"delete", &delete, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL|INSERT},
    182 	{"delete-chars-backwards", &delete_chars_backwards, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
    183 	{"delete-chars-backwards-multiline", &delete_chars_backwards_multiline, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
    184 	{"delete-chars-forwards", &delete_chars_forwards, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
    185 	{"delete-chars-forwards-multiline", &delete_chars_forwards_multiline, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
    186 	{"delete-graphemes-backwards", &delete_graphemes_backwards, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
    187 	{"delete-graphemes-backwards-multiline", &delete_graphemes_backwards_multiline, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
    188 	{"delete-graphemes-forwards", &delete_graphemes_forwards, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
    189 	{"delete-graphemes-forwards-multiline", &delete_graphemes_forwards_multiline, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
    190 	{"delete-to-eol", &delete_to_eol, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
    191 	{"enter-commandedit", &enter_commandedit, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    192 	{"enter-insert", &enter_insert, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL},
    193 	{"enter-searchedit-backwards", &enter_searchedit_backward, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT|VISUAL},
    194 	{"enter-searchedit-forwards", &enter_searchedit_forward, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT|VISUAL},
    195 	{"enter-visual", &enter_visual, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
    196 	{"find-char-backwards", &find_char_backwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    197 	{"find-char-forwards", &find_char_forwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    198 	{"find-next-char-backwards", &find_next_char_backwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    199 	{"find-next-char-forwards", &find_next_char_forwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    200 	{"insert-at-beginning", &insert_at_beginning, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
    201 	{"insert-mark", &insert_mark, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    202 	{"insert-text", &insert_mode_insert_text, KEY_FLAG_JUMP_TO_CURSOR, INSERT},
    203 	{"join-lines", &join_lines, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
    204 	{"jump-to-mark", &jump_to_mark, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    205 	{"key-0", &key_0, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    206 	{"lowercase", &lowercase, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
    207 	{"move-to-eol", &move_to_eol, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    208 	{"move-to-line", &move_to_line, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    209 	{"next-bigword", &next_bigword, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    210 	{"next-bigword-end", &next_bigword_end, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    211 	{"next-word", &next_word, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    212 	{"next-word-end", &next_word_end, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    213 	{"paste-buffer", &paste_normal, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
    214 	{"paste-buffer-backwards", &paste_normal_backwards, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
    215 	{"previous-bigword", &prev_bigword, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    216 	{"previous-word", &prev_word, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    217 	{"push-0", &push_0, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    218 	{"push-1", &push_1, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    219 	{"push-2", &push_2, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    220 	{"push-3", &push_3, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    221 	{"push-4", &push_4, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    222 	{"push-5", &push_5, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    223 	{"push-6", &push_6, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    224 	{"push-7", &push_7, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    225 	{"push-8", &push_8, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    226 	{"push-9", &push_9, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    227 	{"redo", &redo, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
    228 	{"repeat-command", &repeat_command, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
    229 	{"replace", &replace, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
    230 	{"return-to-normal", &return_to_normal, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    231 	{"screen-down", &screen_down, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT},
    232 	{"screen-up", &screen_up, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT},
    233 	{"scroll-lines-down", &scroll_lines_down, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT},
    234 	{"scroll-lines-up", &scroll_lines_up, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT},
    235 	{"scroll-with-cursor-down", &scroll_with_cursor_down, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT},
    236 	{"scroll-with-cursor-up", &scroll_with_cursor_up, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT},
    237 	{"search-next", &key_search_next, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT|VISUAL},
    238 	{"search-previous", &key_search_prev, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT|VISUAL},
    239 	{"show-line", &show_line, KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    240 	{"switch-selection-end", &switch_selection_end, KEY_FLAG_JUMP_TO_CURSOR, VISUAL},
    241 	{"toggle-hard-line-based", &toggle_hard_line_based, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    242 	{"undo", &undo, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
    243 	{"uppercase", &uppercase, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
    244 	{"yank", &yank, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
    245 	{"yank-lines", &yank_lines, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT},
    246 };
    247 
    248 GEN_CB_MAP_HELPERS(basic_key_cb_map, basic_key_cb, text)
    249 
    250 /***************************************************
    251  * General global variables and utility functions. *
    252  ***************************************************/
    253 
    254 enum key_type {
    255 	KEY_INVALID = 0,
    256 	KEY_MOTION_CHAR = 4,
    257 	KEY_MOTION_LINE = 8,
    258 	KEY_MOTION = 4|8,
    259 	KEY_MOTIONALLOWED = 16,
    260 	KEY_NUMBER = 32,
    261 	KEY_NUMBERALLOWED = 64,
    262 	KEY_ANY = 0xFF
    263 };
    264 
    265 /* note: this is supposed to be global for all views/buffers */
    266 static int paste_buffer_line_based = 0;
    267 static txtbuf *paste_buffer = NULL;
    268 static int last_lines_scrolled = -1;
    269 
    270 struct repetition_stack_elem {
    271 	char *key_text;
    272 	size_t len;
    273 	KeySym sym;
    274 	unsigned int key_state;
    275 	int lang_index;
    276 };
    277 
    278 static struct {
    279 	int replaying;
    280 	size_t len, alloc, cur_pos;
    281 	struct repetition_stack_elem *stack;
    282 	size_t tmp_len, tmp_alloc;
    283 	struct repetition_stack_elem *tmp_stack;
    284 } repetition_stack = {0, 0, 0, 0, NULL, 0, 0, NULL};
    285 
    286 typedef void (*motion_callback)(ledit_view *view, size_t line, size_t char_pos, enum key_type type);
    287 
    288 struct key_stack_elem {
    289 	enum key_type key;
    290 	enum key_type followup; /* allowed keys to complete the keybinding */
    291 	/* callback function that motion commands call to complete a command -
    292 	 * line and char_pos already include the repetition stored in this stack
    293 	 * element; type is the type of motion command (used to determine if
    294 	 * the command should operate on lines or chars) */
    295 	motion_callback motion_cb;
    296 	int count; /* number of repetitions */
    297 };
    298 
    299 static struct {
    300 	size_t len, alloc;
    301 	struct key_stack_elem *stack;
    302 } key_stack = {0, 0, NULL};
    303 
    304 static struct action (*grab_char_cb)(ledit_view *view, char *text, size_t len) = NULL;
    305 
    306 void
    307 basic_key_cleanup(void) {
    308 	/* this should be safe since push_repetition_stack sets all new
    309 	   elements to NULL when resizing the stack */
    310 	for (size_t i = 0; i < repetition_stack.alloc; i++) {
    311 		free(repetition_stack.stack[i].key_text);
    312 	}
    313 	for (size_t i = 0; i < repetition_stack.tmp_alloc; i++) {
    314 		free(repetition_stack.tmp_stack[i].key_text);
    315 	}
    316 	free(repetition_stack.stack);
    317 	free(repetition_stack.tmp_stack);
    318 	free(key_stack.stack);
    319 }
    320 
    321 /* No, this isn't actually a stack. So what? */
    322 static struct repetition_stack_elem *push_repetition_stack(void);
    323 static void finalize_repetition_stack(void);
    324 static struct repetition_stack_elem *get_cur_repetition_stack_elem(void);
    325 static void unwind_repetition_stack(void);
    326 static void advance_repetition_stack(void);
    327 static void discard_repetition_stack(void);
    328 
    329 static int key_stack_empty(void);
    330 static struct key_stack_elem *push_key_stack(void);
    331 static struct key_stack_elem *peek_key_stack(void);
    332 static struct key_stack_elem *pop_key_stack(void);
    333 void clear_key_stack(void);
    334 
    335 static void move_cursor_left_right(ledit_view *view, int dir, int allow_illegal_index);
    336 static void move_cursor_up_down(ledit_view *view, int dir);
    337 static void push_num(ledit_view *view, int num);
    338 static void delete_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type);
    339 static void yank_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type);
    340 static void get_new_line_softline(
    341     ledit_view *view, size_t cur_line, size_t cur_index,
    342     int movement, size_t *new_line_ret, int *new_softline_ret
    343 );
    344 static void move_cursor_logically(ledit_view *view, int movement_dir, int allow_illegal_index);
    345 static void change_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type);
    346 static void push_undo_empty_insert(ledit_view *view, size_t line, size_t index, int start_group);
    347 static void move_half_screen(ledit_view *view, int movement);
    348 
    349 static struct action
    350 view_locked_error(ledit_view *view) {
    351 	window_show_message(view->window, view->lock_text, -1);
    352 	return (struct action){ACTION_NONE, NULL};
    353 }
    354 
    355 #define CHECK_VIEW_LOCKED(view) if (view->lock_text) return view_locked_error(view)
    356 #define CHECK_VIEW_LOCKED_NORETURN(view) if (view->lock_text) (void)view_locked_error(view)
    357 
    358 static int
    359 key_stack_empty(void) {
    360 	return key_stack.len == 0;
    361 }
    362 
    363 static struct key_stack_elem *
    364 push_key_stack(void) {
    365 	struct key_stack_elem *e;
    366 	if (key_stack.len >= key_stack.alloc) {
    367 		size_t new_alloc = ideal_array_size(key_stack.alloc, add_sz(key_stack.len, 1));
    368 		key_stack.stack = ledit_reallocarray(
    369 		    key_stack.stack, new_alloc, sizeof(struct key_stack_elem)
    370 		);
    371 		key_stack.alloc = new_alloc;
    372 	}
    373 	e = &key_stack.stack[key_stack.len];
    374 	e->key = KEY_INVALID;
    375 	e->followup = KEY_INVALID;
    376 	e->motion_cb = NULL;
    377 	e->count = 0;
    378 	key_stack.len++;
    379 	return e;
    380 }
    381 
    382 /* Note: for peek and pop, the returned element is only valid
    383  * until the next element is pushed */
    384 /* Note on the note: that's not entirely true for peek */
    385 static struct key_stack_elem *
    386 peek_key_stack(void) {
    387 	if (key_stack.len > 0)
    388 		return &key_stack.stack[key_stack.len - 1];
    389 	return NULL;
    390 }
    391 
    392 static struct key_stack_elem *
    393 pop_key_stack(void) {
    394 	if (key_stack.len > 0) {
    395 		key_stack.len--;
    396 		return &key_stack.stack[key_stack.len];
    397 	}
    398 	return NULL;
    399 }
    400 
    401 void
    402 clear_key_stack(void) {
    403 	key_stack.len = 0;
    404 }
    405 
    406 static struct action
    407 err_invalid_key(ledit_view *view) {
    408 	window_show_message(view->window, "Invalid key", -1);
    409 	clear_key_stack();
    410 	discard_repetition_stack();
    411 	return (struct action){ACTION_NONE, NULL};
    412 }
    413 
    414 /*
    415  * Get the number of times a command should be repeated and the motion
    416  * callback, if there was one on the stack.
    417  * Note that *cb_ret is set to NULL if a non-null address is given and
    418  * there is no motion callback on the stack.
    419  * An empty stack or a stack where the top element has a count of 0
    420  * leads to 0 being returned.
    421  * In case of error, -1 is returned:
    422  * - When the top or second element is not a number key but also does
    423  *   not comtain a motion callback.
    424  * - When the stack is not empty after removing the top element and
    425  *   possibly a second one if the top one was a number key.
    426  */
    427 static int
    428 get_key_repeat_and_motion_cb(ledit_view *view, motion_callback *cb_ret) {
    429 	int num = 1;
    430 	struct key_stack_elem *e = pop_key_stack();
    431 	if (e != NULL) {
    432 		if (e->key & KEY_NUMBER) {
    433 			num = e->count;
    434 			e = pop_key_stack();
    435 		} else if (e->count == 0) {
    436 			num = 0;
    437 		}
    438 		if (e != NULL) {
    439 			int new_count = e->count > 0 ? e->count : 1;
    440 			if (INT_MAX / new_count < num) {
    441 				window_show_message(
    442 				    view->window,
    443 				    "Integer overflow in key repetition", -1
    444 				);
    445 				num = INT_MAX;
    446 			}
    447 			num *= new_count;
    448 		}
    449 	} else {
    450 		num = 0;
    451 	}
    452 	if (e != NULL && !(e->key & KEY_NUMBER) && e->motion_cb == NULL)
    453 		num = -1;
    454 	else if (!key_stack_empty())
    455 		num = -1;
    456 	if (cb_ret != NULL && e != NULL && e->motion_cb != NULL)
    457 		*cb_ret = e->motion_cb;
    458 	else if (cb_ret != NULL)
    459 		*cb_ret = NULL;
    460 	return num;
    461 }
    462 
    463 /*
    464  * Get the number of times a command should be repeated, or -1 if anything
    465  * invalid was on the stack - this is for commands that just take a repeat
    466  * count and nothing else (cursor movement keys are different because they
    467  * can use other elements on the key stack too, for instance call a callback
    468  * as is done for deletion.
    469  * Note that an empty stack leads to 0 being returned even though most commands
    470  * use 1 as repeat then so the caller can distinguish between empty stack and
    471  * a repetition count of 1.
    472  */
    473 static int
    474 get_key_repeat(void) {
    475 	int num = 1;
    476 	struct key_stack_elem *e = pop_key_stack();
    477 	if (e != NULL) {
    478 		if (e->key & KEY_NUMBER) {
    479 			num = e->count > 0 ? e->count : 1;
    480 			e = pop_key_stack();
    481 		}
    482 		if (e != NULL) {
    483 			/* the key was not a number, or there was another
    484 			   element under it on the stack -> error */
    485 			num = -1;
    486 		}
    487 	} else {
    488 		num = 0;
    489 	}
    490 	clear_key_stack();
    491 	return num;
    492 }
    493 
    494 static struct repetition_stack_elem *
    495 push_repetition_stack(void) {
    496 	struct repetition_stack_elem *e;
    497 	if (repetition_stack.tmp_len >= repetition_stack.tmp_alloc) {
    498 		size_t new_alloc = ideal_array_size(repetition_stack.tmp_alloc, add_sz(repetition_stack.tmp_len, 1));
    499 		repetition_stack.tmp_stack = ledit_reallocarray(
    500 		    repetition_stack.tmp_stack,
    501 		    new_alloc, sizeof(struct repetition_stack_elem)
    502 		);
    503 		for (size_t i = repetition_stack.tmp_alloc; i < new_alloc; i++) {
    504 			repetition_stack.tmp_stack[i].key_text = NULL;
    505 		}
    506 		repetition_stack.tmp_alloc = new_alloc;
    507 	}
    508 	e = &repetition_stack.tmp_stack[repetition_stack.tmp_len];
    509 	e->key_text = NULL;
    510 	e->len = 0;
    511 	e->sym = 0;
    512 	e->key_state = 0;
    513 	e->lang_index = 0;
    514 	repetition_stack.tmp_len++;
    515 	return e;
    516 }
    517 
    518 static struct repetition_stack_elem *
    519 get_cur_repetition_stack_elem(void) {
    520 	if (repetition_stack.cur_pos >= repetition_stack.len)
    521 		return NULL;
    522 	return &repetition_stack.stack[repetition_stack.cur_pos];
    523 }
    524 
    525 static void
    526 unwind_repetition_stack(void) {
    527 	repetition_stack.cur_pos = 0;
    528 }
    529 
    530 static void
    531 discard_repetition_stack(void) {
    532 	if (repetition_stack.replaying)
    533 		return;
    534 	for (size_t i = 0; i < repetition_stack.tmp_len; i++) {
    535 		free(repetition_stack.tmp_stack[i].key_text);
    536 		repetition_stack.tmp_stack[i].key_text = NULL;
    537 	}
    538 	repetition_stack.tmp_len = 0;
    539 }
    540 
    541 static void
    542 advance_repetition_stack(void) {
    543 	repetition_stack.cur_pos++;
    544 }
    545 
    546 static void
    547 finalize_repetition_stack(void) {
    548 	if (repetition_stack.replaying)
    549 		return;
    550 	size_t tmp;
    551 	for (size_t i = 0; i < repetition_stack.len; i++) {
    552 		free(repetition_stack.stack[i].key_text);
    553 		repetition_stack.stack[i].key_text = NULL;
    554 	}
    555 	struct repetition_stack_elem *tmpstack;
    556 	repetition_stack.len = repetition_stack.tmp_len;
    557 	repetition_stack.tmp_len = 0;
    558 	tmp = repetition_stack.alloc;
    559 	repetition_stack.alloc = repetition_stack.tmp_alloc;
    560 	repetition_stack.tmp_alloc = tmp;
    561 	tmpstack = repetition_stack.stack;
    562 	repetition_stack.stack = repetition_stack.tmp_stack;
    563 	repetition_stack.tmp_stack = tmpstack;
    564 }
    565 
    566 /* get the new line and softline when moving 'movement' softlines
    567    (or hardlines if hard_line_based is set) up or
    568    down (negative means up, positive means down) */
    569 static void
    570 get_new_line_softline(
    571     ledit_view *view, size_t cur_line, size_t cur_index, int movement,
    572     size_t *new_line_ret, int *new_softline_ret) {
    573 	if (view->buffer->hard_line_based) {
    574 		if (movement < 0 && (size_t)-movement > cur_line)
    575 			*new_line_ret = 0;
    576 		else
    577 			*new_line_ret = cur_line + movement;
    578 		if (*new_line_ret >= view->lines_num)
    579 			*new_line_ret = view->lines_num - 1;
    580 		*new_softline_ret = 0;
    581 	} else {
    582 		int softline = view_pos_to_softline(view, cur_line, cur_index);
    583 		if (movement > 0) {
    584 			int softlines = view_get_softline_count(view, cur_line);
    585 			if (softlines - softline > movement) {
    586 				*new_line_ret = cur_line;
    587 				*new_softline_ret = softline + movement;
    588 			} else {
    589 				movement -= (softlines - softline - 1);
    590 				size_t endline = cur_line + 1;
    591 				while (movement > 0 && endline < view->lines_num) {
    592 					softlines = view_get_softline_count(view, endline);
    593 					movement -= softlines;
    594 					endline++;
    595 				}
    596 				endline--;
    597 				if (movement <= 0) {
    598 					*new_softline_ret = movement + softlines - 1;
    599 				} else {
    600 					*new_softline_ret = softlines - 1;
    601 				}
    602 				*new_line_ret = endline;
    603 			}
    604 		} else if (movement < 0) {
    605 			int softlines = 0;
    606 			if (softline + movement >= 0) {
    607 				*new_line_ret = cur_line;
    608 				*new_softline_ret = softline + movement;
    609 			} else {
    610 				movement += softline;
    611 				size_t endline = cur_line;
    612 				while (movement < 0 && endline > 0) {
    613 					softlines = view_get_softline_count(view, endline-1);
    614 					movement += softlines;
    615 					endline--;
    616 				}
    617 				if (movement >= 0) {
    618 					*new_softline_ret = movement;
    619 				} else {
    620 					*new_softline_ret = 0;
    621 				}
    622 				*new_line_ret = endline;
    623 			}
    624 		} else {
    625 			*new_line_ret = cur_line;
    626 			*new_softline_ret = softline;
    627 		}
    628 	}
    629 }
    630 
    631 /* FIXME: don't overwrite view->cur_line, etc. here? */
    632 static void
    633 delete_range(
    634     ledit_view *view,
    635     int line_based, int selected,
    636     size_t line_index1, size_t byte_index1,
    637     size_t line_index2, size_t byte_index2,
    638     int copy_to_buffer) {
    639 	(void)selected; /* FIXME */
    640 	if (copy_to_buffer && !paste_buffer)
    641 		paste_buffer = txtbuf_new();
    642 	txtbuf *buf = copy_to_buffer ? paste_buffer : NULL;
    643 	enum delete_mode delmode = DELETE_CHAR;
    644 	if (line_based) {
    645 		if (view->buffer->hard_line_based)
    646 			delmode = DELETE_HARDLINE;
    647 		else
    648 			delmode = DELETE_SOFTLINE;
    649 	}
    650 	view_delete_range(
    651 	    view, delmode, 1,
    652 	    line_index1, byte_index1,
    653 	    line_index2, byte_index2,
    654 	    &view->cur_line, &view->cur_index,
    655 	    buf
    656 	);
    657 }
    658 
    659 /* FIXME: better interface for this; documentation */
    660 static void
    661 insert_text(
    662     ledit_view *view,
    663     size_t line, size_t index,
    664     char *text, size_t len,
    665     size_t cur_line1, size_t cur_index1,
    666     size_t cur_line2, size_t cur_index2,
    667     int set_range_start, int set_range_end, int start_group) {
    668 	ledit_range cur_range;
    669 	if (set_range_start) {
    670 		cur_range.line1 = cur_line1;
    671 		cur_range.byte1 = cur_index1;
    672 	} else {
    673 		cur_range.line1 = view->cur_line;
    674 		cur_range.byte1 = view->cur_index;
    675 	}
    676 	/* this is mainly for pasting, where the new line and index
    677 	   should not be at the end of the pasted text */
    678 	if (set_range_end) {
    679 		cur_range.line2 = cur_line2;
    680 		cur_range.byte2 = cur_index2;
    681 	} else {
    682 		/* to make static analysis happy */
    683 		cur_range.line2 = cur_range.byte2 = 0;
    684 	}
    685 	/* FIXME: why did I ever decide to make set_range_end
    686 	   mean exactly the opposite for the two functions? */
    687 	buffer_insert_with_undo(
    688 	    view->buffer, cur_range, !set_range_end,
    689 	    start_group, view->mode,
    690 	    line, index, text, len,
    691 	    &view->cur_line, &view->cur_index
    692 	);
    693 	if (set_range_end) {
    694 		view->cur_line = cur_line2;
    695 		view->cur_index = cur_index2;
    696 	}
    697 }
    698 
    699 static int
    700 delete_selection(ledit_view *view) {
    701 	if (view->sel_valid && (view->sel.line1 != view->sel.line2 || view->sel.byte1 != view->sel.byte2)) {
    702 		delete_range(
    703 		    view, 0, 0,
    704 		    view->sel.line1, view->sel.byte1,
    705 		    view->sel.line2, view->sel.byte2, 1
    706 		);
    707 		paste_buffer_line_based = 0;
    708 		view->sel_valid = 0;
    709 		view->sel.line1 = view->sel.line2 = view->cur_line;
    710 		view->sel.byte1 = view->sel.byte2 = view->cur_index;
    711 		view_wipe_line_cursor_attrs(view, view->cur_line);
    712 		return 1;
    713 	}
    714 	return 0;
    715 }
    716 
    717 /********************************************
    718  * Functions that were declared at the top. *
    719  ********************************************/
    720 
    721 /* used to set cursor - I guess this is sort of a hack */
    722 static void
    723 push_undo_empty_insert(ledit_view *view, size_t line, size_t index, int start_group) {
    724 	/* WARNING: Don't abuse txtbuf like this unless you're stupid like me. */
    725 	txtbuf ins_buf = {.text = "", .len = 0, .cap = 0};
    726 	ledit_range range = {.line1 = line, .byte1 = index, .line2 = line, .byte2 = index};
    727 	undo_push_insert(
    728 	    view->buffer->undo, &ins_buf, range, range, start_group, view->mode
    729 	);
    730 }
    731 
    732 static struct action
    733 append_line_above(ledit_view *view, char *text, size_t len) {
    734 	size_t start, end;
    735 	/* do this here already so the mode group is the same for the newline insertion */
    736 	enter_insert(view, text, len);
    737 	view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start, &end);
    738 	if (view->buffer->hard_line_based || start == 0) {
    739 		insert_text(view, view->cur_line, 0, "\n", 1, 0, 0, view->cur_line, 0, 0, 1, 1);
    740 	} else {
    741 		/* FIXME: this interface really is horrible */
    742 		insert_text(view, view->cur_line, start, "\n\n", 2, 0, 0, view->cur_line + 1, 0, 0, 1, 1);
    743 	}
    744 	return (struct action){ACTION_NONE, NULL};
    745 }
    746 
    747 static struct action
    748 append_line_below(ledit_view *view, char *text, size_t len) {
    749 	size_t start, end;
    750 	enter_insert(view, text, len);
    751 	view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start, &end);
    752 	ledit_line *ll = buffer_get_line(view->buffer, view->cur_line);
    753 	if (view->buffer->hard_line_based || end == ll->len) {
    754 		insert_text(view, view->cur_line, ll->len, "\n", 1, 0, 0, view->cur_line + 1, 0, 0, 1, 1);
    755 	} else {
    756 		insert_text(view, view->cur_line, end, "\n\n", 2, 0, 0, view->cur_line + 1, 0, 0, 1, 1);
    757 	}
    758 	return (struct action){ACTION_NONE, NULL};
    759 }
    760 
    761 static struct action
    762 append_after_cursor(ledit_view *view, char *text, size_t len) {
    763 	enter_insert(view, text, len);
    764 	/* make cursor jump back to original position on undo */
    765 	push_undo_empty_insert(view, view->cur_line, view->cur_index, 1);
    766 	view_next_cursor_pos(
    767 	    view, view->cur_line, view->cur_index, 1, 0, NULL, &view->cur_index
    768 	);
    769 	return (struct action){ACTION_NONE, NULL};
    770 }
    771 
    772 static struct action
    773 append_after_eol(ledit_view *view, char *text, size_t len) {
    774 	size_t start, end;
    775 	enter_insert(view, text, len);
    776 	view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start, &end);
    777 	/* make cursor jump back to original position on undo */
    778 	push_undo_empty_insert(view, view->cur_line, view->cur_index, 1);
    779 	ledit_line *ll = buffer_get_line(view->buffer, view->cur_line);
    780 	if (view->buffer->hard_line_based)
    781 		view->cur_index = ll->len;
    782 	else
    783 		view->cur_index = end;
    784 	return (struct action){ACTION_NONE, NULL};
    785 }
    786 
    787 static struct action
    788 move_to_line(ledit_view *view, char *text, size_t len) {
    789 	(void)text;
    790 	(void)len;
    791 	motion_callback cb = NULL;
    792 	int repeat = get_key_repeat_and_motion_cb(view, &cb);
    793 	size_t line;
    794 	if (repeat > 0)
    795 		line = (size_t)repeat > view->lines_num ? view->lines_num : (size_t)repeat;
    796 	else if (repeat == 0)
    797 		line = view->lines_num;
    798 	else
    799 		return err_invalid_key(view);
    800 	if (cb != NULL) {
    801 		/* this is a bit of a hack - because move_to_line always works
    802 		   with hard lines, it sets the index to ll->len so e.g. the delete
    803 		   callback deletes until the end of the line even in soft line mode */
    804 		ledit_line *ll = buffer_get_line(view->buffer, line - 1);
    805 		cb(view, line - 1, ll->len, KEY_MOTION_LINE);
    806 	} else {
    807 		if (view->mode == NORMAL)
    808 			view_wipe_line_cursor_attrs(view, view->cur_line);
    809 		else if (view->mode == VISUAL)
    810 			view_set_selection(view, view->sel.line1, view->sel.byte1, line - 1, 0);
    811 		view->cur_line = line - 1;
    812 		view->cur_index = 0;
    813 		int text_w, text_h;
    814 		window_get_textview_size(view->window, &text_w, &text_h);
    815 		ledit_view_line *vl = view_get_line(view, view->cur_line);
    816 		int x, y, h;
    817 		view_get_cursor_pixel_pos(view, view->cur_line, 0, &x, &y, &h);
    818 		/* if cursor is not on screen anymore, move to middle of screen */
    819 		if (vl->y_offset < view->display_offset ||
    820 		    vl->y_offset + h > view->display_offset + text_h) {
    821 			view_scroll(view, vl->y_offset - text_h / 2);
    822 		}
    823 		if (view->mode == NORMAL)
    824 			view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
    825 		discard_repetition_stack();
    826 	}
    827 	return (struct action){ACTION_NONE, NULL};
    828 }
    829 
    830 /* FIXME: should these scrolling functions change behavior when hard_line_based == 1? */
    831 static void
    832 scroll_lines(ledit_view *view, int lines, int dir) {
    833 	if (last_lines_scrolled <= 0 && lines <= 0) {
    834 		/* no scroll command yet - scroll half of screen */
    835 		move_half_screen(view, dir);
    836 	} else {
    837 		int x, y, h, sli;
    838 		int final_lines, text_w, text_h;
    839 		ledit_view_line *vl = view_get_line(view, view->cur_line);
    840 		view_get_cursor_pixel_pos(view, view->cur_line, view->cur_index, &x, &y, &h);
    841 		/* get the middle position of char */
    842 		view_pos_to_x_softline(view, view->cur_line, view->cur_index, &x, &sli);
    843 		long abs_pos = vl->y_offset + y;
    844 		window_get_textview_size(view->window, &text_w, &text_h);
    845 		if (lines > 0)
    846 			final_lines = last_lines_scrolled = lines;
    847 		else
    848 			final_lines = last_lines_scrolled;
    849 		view_wipe_line_cursor_attrs(view, view->cur_line);
    850 		get_new_line_softline(
    851 		    view, view->cur_line, view->cur_index,
    852 		    dir < 0 ? -final_lines : final_lines,
    853 		    &view->cur_line, &sli
    854 		);
    855 		size_t start, end;
    856 		view_get_softline_bounds(view, view->cur_line, sli, &start, &end);
    857 		vl = view_get_line(view, view->cur_line);
    858 		view->cur_index = view_x_softline_to_pos(view, view->cur_line, x, sli);
    859 		view_get_cursor_pixel_pos(view, view->cur_line, view->cur_index, &x, &y, &h);
    860 		long new_abs_pos = vl->y_offset + y;
    861 		view_scroll(view, view->display_offset + (new_abs_pos - abs_pos));
    862 		view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
    863 	}
    864 }
    865 
    866 static struct action
    867 scroll_lines_up(ledit_view *view, char *text, size_t len) {
    868 	(void)text;
    869 	(void)len;
    870 	int repeat = get_key_repeat();
    871 	if (repeat >= 0)
    872 		scroll_lines(view, repeat, -1);
    873 	else
    874 		window_show_message(view->window, "Invalid key", -1);
    875 	discard_repetition_stack();
    876 	return (struct action){ACTION_NONE, NULL};
    877 }
    878 
    879 static struct action
    880 scroll_lines_down(ledit_view *view, char *text, size_t len) {
    881 	(void)text;
    882 	(void)len;
    883 	int repeat = get_key_repeat();
    884 	if (repeat >= 0)
    885 		scroll_lines(view, repeat, 1);
    886 	else
    887 		window_show_message(view->window, "Invalid key", -1);
    888 	discard_repetition_stack();
    889 	return (struct action){ACTION_NONE, NULL};
    890 }
    891 
    892 static void
    893 scroll_with_cursor(ledit_view *view, int movement) {
    894 	int x, y, h;
    895 	view_get_cursor_pixel_pos(view, view->cur_line, view->cur_index, &x, &y, &h);
    896 	int pix_movement = movement * h; /* FIXME: overflow */
    897 	view_scroll(view, view->display_offset + pix_movement);
    898 	size_t old_line = view->cur_line;
    899 	size_t old_index = view->cur_index;
    900 	view_get_nearest_legal_pos(
    901 	    view, old_line, old_index,
    902 	    &view->cur_line, &view->cur_index
    903 	);
    904 	if (old_line != view->cur_line || old_index != view->cur_index) {
    905 		view_wipe_line_cursor_attrs(view, old_line);
    906 		/* if cursor is at top or bottom of screen, snap it to the
    907 		   very edge to avoid it looking weird */
    908 		if (movement > 0) {
    909 			view_scroll_to_pos_top(
    910 			    view, view->cur_line, view->cur_index
    911 			);
    912 		} else {
    913 			view_scroll_to_pos_bottom(
    914 			    view, view->cur_line, view->cur_index
    915 			);
    916 		}
    917 	}
    918 	if (view->mode == NORMAL) {
    919 		view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view->cur_index);
    920 		view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
    921 	}
    922 }
    923 
    924 static struct action
    925 scroll_with_cursor_up(ledit_view *view, char *text, size_t len) {
    926 	(void)text;
    927 	(void)len;
    928 	int repeat = get_key_repeat();
    929 	if (repeat >= 0)
    930 		scroll_with_cursor(view, -(repeat == 0 ? 1 : repeat));
    931 	else
    932 		window_show_message(view->window, "Invalid key", -1);
    933 	discard_repetition_stack();
    934 	return (struct action){ACTION_NONE, NULL};
    935 }
    936 
    937 static struct action
    938 scroll_with_cursor_down(ledit_view *view, char *text, size_t len) {
    939 	(void)text;
    940 	(void)len;
    941 	int repeat = get_key_repeat();
    942 	if (repeat >= 0)
    943 		scroll_with_cursor(view, repeat == 0 ? 1 : repeat);
    944 	else
    945 		window_show_message(view->window, "Invalid key", -1);
    946 	discard_repetition_stack();
    947 	return (struct action){ACTION_NONE, NULL};
    948 }
    949 
    950 /* FIXME: Make all these scrolling functions work in visual mode */
    951 /* movement is multiplied with half the window height and the result is added to the display offset
    952    the cursor is moved to the bottom if movement is upwards, to the top otherwise
    953    FIXME: this is slightly different now
    954    (unless the screen is already at the very top or bottom - then it is the other way around) */
    955 /* FIXME: this is a bit weird at the moment */
    956 static void
    957 move_half_screen(ledit_view *view, int movement) {
    958 	int w, h;
    959 	window_get_textview_size(view->window, &w, &h);
    960 	/* FIXME: overflow */
    961 	int total = movement * h/2;
    962 	ledit_view_line *vl = view_get_line(view, view->cur_line);
    963 	int cur_x, cur_y, cur_h;
    964 	view_get_cursor_pixel_pos(
    965 	    view, view->cur_line, view->cur_index, &cur_x, &cur_y, &cur_h
    966 	);
    967 	long real_cur_y = vl->y_offset - view->display_offset + cur_y;
    968 	/* new pixel position of cursor */
    969 	/* Note: this usually causes at least part of a line of overlap
    970 	   because ensure_cursor_shown scrolls back a bit if the line
    971 	   isn't completely shown (this behavior could be changed using
    972 	   view_get_nearest_legal_pos) */
    973 	int y = movement > 0 ? 0 : h;
    974 	int half_screen = abs(movement) % 2 == 1;
    975 	if (half_screen) {
    976 		/* if only half screens are moved and we are at the beginning or
    977 		   end, just move the cursor the movement amount instead of
    978 		   moving it to the very top or bottom */
    979 		if (view->display_offset + total <= 0 ||
    980 		    view->display_offset + total + h >= view->total_height) {
    981 			y = real_cur_y + total;
    982 		}
    983 	} else {
    984 		if (view->display_offset + total <= 0)
    985 			y = 0;
    986 		else if (view->display_offset + total + h > view->total_height)
    987 			y = h;
    988 	}
    989 	if (y < 0)
    990 		y = 0;
    991 	if (y > h)
    992 		y = h;
    993 	view_scroll(view, view->display_offset + total);
    994 	view_wipe_line_cursor_attrs(view, view->cur_line);
    995 	/* try to keep current x position of cursor */
    996 	int x, softline;
    997 	/* FIXME: properly document what uses PANGO_SCALE and what not */
    998 	view_pos_to_x_softline(view, view->cur_line, view->cur_index, &x, &softline);
    999 	view_xy_to_line_byte(
   1000 	    view, x / PANGO_SCALE, y, 0,
   1001 	    &view->cur_line, &view->cur_index
   1002 	);
   1003 	if (view->mode == NORMAL) {
   1004 		view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view->cur_index);
   1005 		view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
   1006 	}
   1007 }
   1008 
   1009 static struct action
   1010 screen_up(ledit_view *view, char *text, size_t len) {
   1011 	(void)text;
   1012 	(void)len;
   1013 	int repeat = get_key_repeat();
   1014 	/* FIXME: overflow */
   1015 	if (repeat >= 0)
   1016 		move_half_screen(view, -(repeat == 0 ? 2 : repeat*2));
   1017 	else
   1018 		window_show_message(view->window, "Invalid key", -1);
   1019 	discard_repetition_stack();
   1020 	return (struct action){ACTION_NONE, NULL};
   1021 }
   1022 
   1023 static struct action
   1024 screen_down(ledit_view *view, char *text, size_t len) {
   1025 	(void)text;
   1026 	(void)len;
   1027 	int repeat = get_key_repeat();
   1028 	if (repeat >= 0)
   1029 		move_half_screen(view, repeat == 0 ? 2 : repeat*2);
   1030 	else
   1031 		window_show_message(view->window, "Invalid key", -1);
   1032 	discard_repetition_stack();
   1033 	return (struct action){ACTION_NONE, NULL};
   1034 }
   1035 
   1036 static struct action
   1037 delete_to_eol(ledit_view *view, char *text, size_t len) {
   1038 	(void)text;
   1039 	(void)len;
   1040 	if (!key_stack_empty())
   1041 		return err_invalid_key(view);
   1042 	size_t start, end;
   1043 	ledit_line *ll = buffer_get_line(view->buffer, view->cur_line);
   1044 	if (view->buffer->hard_line_based) {
   1045 		end = ll->len;
   1046 	} else {
   1047 		view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start, &end);
   1048 	}
   1049 	delete_range(
   1050 	    view, 0, 0,
   1051 	    view->cur_line, view->cur_index,
   1052 	    view->cur_line, end, view->mode != INSERT
   1053 	);
   1054 	if (view->mode != INSERT)
   1055 		paste_buffer_line_based = 0;
   1056 	if (view->mode == NORMAL) {
   1057 		view->cur_index = view_get_legal_normal_pos(
   1058 		    view, view->cur_line, view->cur_index
   1059 		);
   1060 		view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
   1061 	}
   1062 	return (struct action){ACTION_NONE, NULL};
   1063 }
   1064 
   1065 static struct action
   1066 change_to_eol(ledit_view *view, char *text, size_t len) {
   1067 	(void)text;
   1068 	(void)len;
   1069 	if (!key_stack_empty())
   1070 		return err_invalid_key(view);
   1071 	view_set_mode(view, INSERT);
   1072 	size_t start, end;
   1073 	ledit_line *ll = buffer_get_line(view->buffer, view->cur_line);
   1074 	if (view->buffer->hard_line_based) {
   1075 		end = ll->len;
   1076 	} else {
   1077 		view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start, &end);
   1078 	}
   1079 	delete_range(
   1080 	    view, 0, 0,
   1081 	    view->cur_line, view->cur_index,
   1082 	    view->cur_line, end, 1
   1083 	);
   1084 	paste_buffer_line_based = 0;
   1085 	view_wipe_line_cursor_attrs(view, view->cur_line);
   1086 	return (struct action){ACTION_NONE, NULL};
   1087 }
   1088 
   1089 /* FIXME: clear selection on most commands */
   1090 /* FIXME: don't include escape when repeating change with '.'? */
   1091 static struct action
   1092 change(ledit_view *view, char *text, size_t len) {
   1093 	(void)text;
   1094 	(void)len;
   1095 	motion_callback cb = NULL;
   1096 	int num = get_key_repeat_and_motion_cb(view, &cb);
   1097 	if (num == -1)
   1098 		return err_invalid_key(view);
   1099 	if (view->mode == VISUAL) {
   1100 		view_set_mode(view, INSERT);
   1101 		delete_selection(view);
   1102 		view_wipe_line_cursor_attrs(view, view->cur_line);
   1103 		clear_key_stack();
   1104 	} else {
   1105 		if (cb == &change_cb) {
   1106 			int lines = num > 0 ? num : 1;
   1107 			size_t new_line;
   1108 			int new_softline;
   1109 			get_new_line_softline(
   1110 			    view, view->cur_line, view->cur_index,
   1111 			    lines - 1, &new_line, &new_softline
   1112 			);
   1113 			size_t start, end;
   1114 			view_get_softline_bounds(view, new_line, new_softline, &start, &end);
   1115 			cb(view, new_line, start, KEY_MOTION_LINE);
   1116 			clear_key_stack();
   1117 		} else if (cb != NULL) {
   1118 			return err_invalid_key(view);
   1119 		} else {
   1120 			struct key_stack_elem *e = push_key_stack();
   1121 			e->key = KEY_MOTIONALLOWED|KEY_NUMBERALLOWED;
   1122 			e->count = num;
   1123 			e->motion_cb = &change_cb;
   1124 		}
   1125 	}
   1126 	return (struct action){ACTION_NONE, NULL};
   1127 }
   1128 
   1129 static void
   1130 change_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type) {
   1131 	CHECK_VIEW_LOCKED_NORETURN(view);
   1132 	/* set mode first so the deletion is included in the undo group */
   1133 	view_set_mode(view, INSERT);
   1134 	int line_based = type == KEY_MOTION_LINE ? 1 : 0;
   1135 	/* this hackery is needed to avoid deleting the entire last line and
   1136 	   instead leave an empty line - this should be made nicer (FIXME) */
   1137 	size_t pos1 = view->cur_index, pos2 = char_pos;
   1138 	if (line_based && !view->buffer->hard_line_based) {
   1139 		size_t pos1, pos2, tmp;
   1140 		view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &pos1, &tmp);
   1141 		view_get_pos_softline_bounds(view, line, char_pos, &tmp, &pos2);
   1142 	} else if (line_based && view->buffer->hard_line_based) {
   1143 		pos1 = 0;
   1144 		ledit_line *ll = buffer_get_line(view->buffer, line);
   1145 		pos2 = ll->len;
   1146 	}
   1147 	/* force line_based to 0 (see comment about hackery above) */
   1148 	delete_range(
   1149 	    view, 0, 0,
   1150 	    view->cur_line, pos1,
   1151 	    line, pos2, view->mode != INSERT
   1152 	);
   1153 	if (view->mode != INSERT)
   1154 		paste_buffer_line_based = line_based;
   1155 	view_wipe_line_cursor_attrs(view, view->cur_line);
   1156 }
   1157 
   1158 static struct action
   1159 yank(ledit_view *view, char *text, size_t len) {
   1160 	(void)text;
   1161 	(void)len;
   1162 	if (!paste_buffer)
   1163 		paste_buffer = txtbuf_new();
   1164 	if (view->mode == VISUAL) {
   1165 		sort_range(
   1166 		    &view->sel.line1, &view->sel.byte1, &view->sel.line2, &view->sel.byte2
   1167 		);
   1168 		buffer_copy_text_to_txtbuf(
   1169 		    view->buffer, paste_buffer,
   1170 		    view->sel.line1, view->sel.byte1, view->sel.line2, view->sel.byte2
   1171 		);
   1172 		paste_buffer_line_based = 0;
   1173 		view->cur_line = view->sel.line1;
   1174 		view->cur_index = view->sel.byte1;
   1175 		view_wipe_selection(view);
   1176 		view_set_mode(view, NORMAL);
   1177 		view->cur_index = view_get_legal_normal_pos(
   1178 		    view, view->cur_line, view->cur_index
   1179 		);
   1180 		view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
   1181 		clear_key_stack();
   1182 	} else {
   1183 		motion_callback cb = NULL;
   1184 		int num = get_key_repeat_and_motion_cb(view, &cb);
   1185 		if (num == -1)
   1186 			return err_invalid_key(view);
   1187 		if (cb == &yank_cb) {
   1188 			if (num == 0)
   1189 				num = 1;
   1190 			size_t new_line;
   1191 			int new_softline;
   1192 			get_new_line_softline(
   1193 			    view, view->cur_line, view->cur_index,
   1194 			    num - 1, &new_line, &new_softline
   1195 			);
   1196 			size_t start, end;
   1197 			view_get_softline_bounds(view, new_line, new_softline, &start, &end);
   1198 			cb(view, new_line, start, KEY_MOTION_LINE);
   1199 			clear_key_stack();
   1200 		} else if (cb == NULL) {
   1201 			struct key_stack_elem *e = push_key_stack();
   1202 			e->key = KEY_MOTIONALLOWED|KEY_NUMBERALLOWED;
   1203 			e->count = num;
   1204 			e->motion_cb = &yank_cb;
   1205 		} else {
   1206 			/* FIXME: proper error */
   1207 			clear_key_stack();
   1208 		}
   1209 	}
   1210 	discard_repetition_stack();
   1211 	return (struct action){ACTION_NONE, NULL};
   1212 }
   1213 
   1214 static struct action
   1215 yank_lines(ledit_view *view, char *text, size_t len) {
   1216 	(void)text;
   1217 	(void)len;
   1218 	int num = get_key_repeat();
   1219 	if (num == -1)
   1220 		return err_invalid_key(view);
   1221 	else if (num == 0)
   1222 		num = 1;
   1223 	size_t new_line;
   1224 	int new_softline;
   1225 	get_new_line_softline(
   1226 	    view, view->cur_line, view->cur_index,
   1227 	    num - 1, &new_line, &new_softline
   1228 	);
   1229 	size_t start, end;
   1230 	view_get_softline_bounds(view, new_line, new_softline, &start, &end);
   1231 	yank_cb(view, new_line, start, KEY_MOTION_LINE);
   1232 	clear_key_stack();
   1233 	return (struct action){ACTION_NONE, NULL};
   1234 }
   1235 
   1236 static void
   1237 yank_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type) {
   1238 	int line_based = type == KEY_MOTION_LINE ? 1 : 0;
   1239 	size_t l1 = view->cur_line, l2 = line, b1 = view->cur_index, b2 = char_pos;
   1240 	if (!paste_buffer)
   1241 		paste_buffer = txtbuf_new();
   1242 	if (l2 < l1 || (l1 == l2 && b2 < b1)) {
   1243 		swap_sz(&l1, &l2);
   1244 		swap_sz(&b1, &b2);
   1245 	}
   1246 	if (line_based && !view->buffer->hard_line_based) {
   1247 		size_t start1, end2, tmp;
   1248 		view_get_pos_softline_bounds(view, l1, b1, &start1, &tmp);
   1249 		view_get_pos_softline_bounds(view, l2, b2, &tmp, &end2);
   1250 		ledit_line *ll = buffer_get_line(view->buffer, l2);
   1251 		if (end2 == ll->len && l2 < view->lines_num - 1) {
   1252 			l2++;
   1253 			end2 = 0;
   1254 		}
   1255 		buffer_copy_text_to_txtbuf(
   1256 		    view->buffer, paste_buffer, l1, start1, l2, end2
   1257 		);
   1258 	} else if (line_based && view->buffer->hard_line_based) {
   1259 		ledit_line *ll = buffer_get_line(view->buffer, l2);
   1260 		size_t end = ll->len;
   1261 		if (l2 < view->lines_num - 1) {
   1262 			l2++;
   1263 			end = 0;
   1264 		}
   1265 		buffer_copy_text_to_txtbuf(
   1266 		    view->buffer, paste_buffer, l1, 0, l2, end
   1267 		);
   1268 	} else {
   1269 		buffer_copy_text_to_txtbuf(
   1270 		    view->buffer, paste_buffer, l1, b1, l2, b2
   1271 		);
   1272 	}
   1273 	paste_buffer_line_based = line_based;
   1274 	discard_repetition_stack();
   1275 }
   1276 
   1277 static struct action
   1278 delete(ledit_view *view, char *text, size_t len) {
   1279 	(void)text;
   1280 	(void)len;
   1281 	motion_callback cb = NULL;
   1282 	int num = get_key_repeat_and_motion_cb(view, &cb);
   1283 	if (num == -1)
   1284 		return err_invalid_key(view);
   1285 	if (delete_selection(view)) {
   1286 		clear_key_stack();
   1287 	} else {
   1288 		/* FIXME: checking equality of the function pointer may be a bit risky */
   1289 		/* -> actually, it shouldn't be a problem */
   1290 		if (cb == &delete_cb) {
   1291 			int lines = num > 0 ? num : 1;
   1292 			size_t new_line;
   1293 			int new_softline;
   1294 			get_new_line_softline(
   1295 			    view, view->cur_line, view->cur_index,
   1296 			    lines - 1, &new_line, &new_softline
   1297 			);
   1298 			size_t start, end;
   1299 			view_get_softline_bounds(view, new_line, new_softline, &start, &end);
   1300 			cb(view, new_line, start, KEY_MOTION_LINE);
   1301 			clear_key_stack();
   1302 		} else if (cb != NULL) {
   1303 			return err_invalid_key(view);
   1304 		} else {
   1305 			struct key_stack_elem *e = push_key_stack();
   1306 			e->key = KEY_MOTIONALLOWED|KEY_NUMBERALLOWED;
   1307 			e->count = num;
   1308 			e->motion_cb = &delete_cb;
   1309 		}
   1310 	}
   1311 	return (struct action){ACTION_NONE, NULL};
   1312 }
   1313 
   1314 /* FIXME: should this get number of lines to remove or actual end line? */
   1315 static void
   1316 delete_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type) {
   1317 	CHECK_VIEW_LOCKED_NORETURN(view);
   1318 	view_wipe_line_cursor_attrs(view, view->cur_line);
   1319 	int line_based = type == KEY_MOTION_LINE ? 1 : 0;
   1320 	delete_range(
   1321 	    view, line_based, 0,
   1322 	    view->cur_line, view->cur_index,
   1323 	    line, char_pos, view->mode != INSERT
   1324 	);
   1325 	if (view->mode != INSERT) {
   1326 		paste_buffer_line_based = line_based;
   1327 		finalize_repetition_stack();
   1328 	}
   1329 	if (view->mode == NORMAL)
   1330 		view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
   1331 }
   1332 
   1333 /* Note that these paste functions are a bit weird when working with softlines -
   1334    they always make sure the pasted text is separated from the surrounding text by
   1335    hard lines, which may be unexpected, but the alternatives I could think of are
   1336    even weirder */
   1337 static struct action
   1338 paste_normal(ledit_view *view, char *text, size_t len) {
   1339 	(void)text;
   1340 	(void)len;
   1341 	if (!paste_buffer) {
   1342 		window_show_message(view->window, "Nothing to paste", -1);
   1343 		discard_repetition_stack();
   1344 		return (struct action){ACTION_NONE, NULL};
   1345 	}
   1346 	if (paste_buffer_line_based) {
   1347 		view_wipe_line_cursor_attrs(view, view->cur_line);
   1348 		ledit_line *ll = buffer_get_line(view->buffer, view->cur_line);
   1349 		size_t brk = 0;
   1350 		if (view->buffer->hard_line_based) {
   1351 			brk = ll->len;
   1352 		} else {
   1353 			size_t tmp;
   1354 			view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &tmp, &brk);
   1355 		}
   1356 		/* FIXME: this is a bit inefficient because insert_text does not
   1357 		   use the *_base functions, but maybe this way is a bit safer */
   1358 		insert_text(
   1359 		    view, view->cur_line, brk,
   1360 		    "\n", 1, 0, 0, view->cur_line, view->cur_index, 0, 1, 1
   1361 		);
   1362 		size_t text_len = paste_buffer->len;
   1363 		ll = buffer_get_line(view->buffer, view->cur_line + 1);
   1364 		if (ll->len == 0 && paste_buffer->text[text_len-1] == '\n') {
   1365 			/* remove trailing newline if it exists and text is already on own line */
   1366 			text_len--;
   1367 			paste_buffer->text[text_len] = '\0';
   1368 		} else if (ll->len > 0 && paste_buffer->text[text_len-1] != '\n') {
   1369 			/* ensure pasted text is on its own hard line */
   1370 			insert_text(
   1371 			    view, view->cur_line + 1, 0,
   1372 			    "\n", 1, 0, 0, view->cur_line, view->cur_index, 0, 1, 0
   1373 			);
   1374 		}
   1375 		insert_text(
   1376 		    view, view->cur_line + 1, 0,
   1377 		    paste_buffer->text, text_len, 0, 0, view->cur_line + 1, 0, 0, 1, 0
   1378 		);
   1379 	} else {
   1380 		size_t old_line = view->cur_line;
   1381 		size_t old_index = view->cur_index;
   1382 		/* must allow illegal index so text can be pasted at end of line */
   1383 		move_cursor_logically(view, 1, 1);
   1384 		insert_text(
   1385 		    view, view->cur_line, view->cur_index,
   1386 		    paste_buffer->text, paste_buffer->len,
   1387 		    old_line, old_index, view->cur_line, view->cur_index, 1, 1, 1
   1388 		);
   1389 	}
   1390 	if (view->mode == NORMAL)
   1391 		view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
   1392 	if (view->mode != INSERT)
   1393 		finalize_repetition_stack();
   1394 	return (struct action){ACTION_NONE, NULL};
   1395 }
   1396 
   1397 static struct action
   1398 paste_normal_backwards(ledit_view *view, char *text, size_t len) {
   1399 	(void)text;
   1400 	(void)len;
   1401 	if (!paste_buffer) {
   1402 		window_show_message(view->window, "Nothing to paste", -1);
   1403 		discard_repetition_stack();
   1404 		return (struct action){ACTION_NONE, NULL};
   1405 	}
   1406 	if (paste_buffer_line_based) {
   1407 		view_wipe_line_cursor_attrs(view, view->cur_line);
   1408 		size_t brk = 0;
   1409 		if (!view->buffer->hard_line_based) {
   1410 			size_t tmp;
   1411 			view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &brk, &tmp);
   1412 		}
   1413 		/* FIXME: better interface without these weird int args */
   1414 		insert_text(
   1415 		    view, view->cur_line, brk,
   1416 		    "\n", 1, 0, 0, view->cur_line, view->cur_index, 0, 1, 1
   1417 		);
   1418 		size_t text_len = paste_buffer->len;
   1419 		ledit_line *ll = buffer_get_line(view->buffer, view->cur_line);
   1420 		if (paste_buffer->text[text_len-1] == '\n') {
   1421 			/* remove trailing newline if it exists */
   1422 			text_len--;
   1423 			paste_buffer->text[text_len] = '\0';
   1424 		}
   1425 		size_t new_line = view->cur_line;
   1426 		if (ll->len > 0) {
   1427 			/* ensure pasted text is on its own hard line */
   1428 			insert_text(
   1429 			    view, view->cur_line, ll->len,
   1430 			    "\n", 1, 0, 0, view->cur_line, view->cur_index, 0, 1, 0
   1431 			);
   1432 			new_line = view->cur_line + 1;
   1433 		}
   1434 		insert_text(
   1435 		    view, new_line, 0,
   1436 		    paste_buffer->text, text_len, 0, 0, new_line, 0, 0, 1, 0
   1437 		);
   1438 	} else {
   1439 		insert_text(
   1440 		    view, view->cur_line, view->cur_index,
   1441 		    paste_buffer->text, paste_buffer->len,
   1442 		    0, 0, view->cur_line, view->cur_index, 0, 1, 1
   1443 		);
   1444 	}
   1445 	if (view->mode == NORMAL)
   1446 		view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
   1447 	if (view->mode != INSERT)
   1448 		finalize_repetition_stack();
   1449 	return (struct action){ACTION_NONE, NULL};
   1450 }
   1451 
   1452 static struct action
   1453 key_0(ledit_view *view, char *text, size_t len) {
   1454 	struct key_stack_elem *e = peek_key_stack();
   1455 	if (!e || (e->key & KEY_MOTIONALLOWED)) {
   1456 		return cursor_to_beginning(view, text, len);
   1457 	} else if (e->key & KEY_NUMBER) {
   1458 		return push_0(view, text, len);
   1459 	} else {
   1460 		return err_invalid_key(view);
   1461 	}
   1462 }
   1463 
   1464 static void
   1465 push_num(ledit_view *view, int num) {
   1466 	struct key_stack_elem *e = peek_key_stack();
   1467 	if (!e || !(e->key & KEY_NUMBER)) {
   1468 		e = push_key_stack();
   1469 		e->key = KEY_NUMBER;
   1470 		e->followup = KEY_NUMBER|KEY_NUMBERALLOWED;
   1471 	}
   1472 	if (INT_MAX / 10 < e->count) {
   1473 		window_show_message(
   1474 		    view->window,
   1475 		    "Integer overflow in key repetition", -1
   1476 		);
   1477 		clear_key_stack();
   1478 		return;
   1479 	}
   1480 	e->count *= 10;
   1481 	if (INT_MAX - num < e->count) {
   1482 		window_show_message(
   1483 		    view->window,
   1484 		    "Integer overflow in key repetition", -1
   1485 		);
   1486 		clear_key_stack();
   1487 		return;
   1488 	}
   1489 	e->count += num;
   1490 }
   1491 
   1492 static struct action
   1493 push_0(ledit_view *view, char *text, size_t len) {
   1494 	(void)view;
   1495 	(void)text;
   1496 	(void)len;
   1497 	push_num(view, 0);
   1498 	return (struct action){ACTION_NONE, NULL};
   1499 }
   1500 
   1501 static struct action
   1502 push_1(ledit_view *view, char *text, size_t len) {
   1503 	(void)view;
   1504 	(void)text;
   1505 	(void)len;
   1506 	push_num(view, 1);
   1507 	return (struct action){ACTION_NONE, NULL};
   1508 }
   1509 
   1510 static struct action
   1511 push_2(ledit_view *view, char *text, size_t len) {
   1512 	(void)view;
   1513 	(void)text;
   1514 	(void)len;
   1515 	push_num(view, 2);
   1516 	return (struct action){ACTION_NONE, NULL};
   1517 }
   1518 
   1519 static struct action
   1520 push_3(ledit_view *view, char *text, size_t len) {
   1521 	(void)view;
   1522 	(void)text;
   1523 	(void)len;
   1524 	push_num(view, 3);
   1525 	return (struct action){ACTION_NONE, NULL};
   1526 }
   1527 
   1528 static struct action
   1529 push_4(ledit_view *view, char *text, size_t len) {
   1530 	(void)view;
   1531 	(void)text;
   1532 	(void)len;
   1533 	push_num(view, 4);
   1534 	return (struct action){ACTION_NONE, NULL};
   1535 }
   1536 
   1537 static struct action
   1538 push_5(ledit_view *view, char *text, size_t len) {
   1539 	(void)view;
   1540 	(void)text;
   1541 	(void)len;
   1542 	push_num(view, 5);
   1543 	return (struct action){ACTION_NONE, NULL};
   1544 }
   1545 
   1546 static struct action
   1547 push_6(ledit_view *view, char *text, size_t len) {
   1548 	(void)view;
   1549 	(void)text;
   1550 	(void)len;
   1551 	push_num(view, 6);
   1552 	return (struct action){ACTION_NONE, NULL};
   1553 }
   1554 
   1555 static struct action
   1556 push_7(ledit_view *view, char *text, size_t len) {
   1557 	(void)view;
   1558 	(void)text;
   1559 	(void)len;
   1560 	push_num(view, 7);
   1561 	return (struct action){ACTION_NONE, NULL};
   1562 }
   1563 
   1564 static struct action
   1565 push_8(ledit_view *view, char *text, size_t len) {
   1566 	(void)view;
   1567 	(void)text;
   1568 	(void)len;
   1569 	push_num(view, 8);
   1570 	return (struct action){ACTION_NONE, NULL};
   1571 }
   1572 
   1573 static struct action
   1574 push_9(ledit_view *view, char *text, size_t len) {
   1575 	(void)view;
   1576 	(void)text;
   1577 	(void)len;
   1578 	push_num(view, 9);
   1579 	return (struct action){ACTION_NONE, NULL};
   1580 }
   1581 
   1582 /* FIXME: function to look at pango property to decide when to delete entire grapheme */
   1583 /* FIXME: The cursor may be in an illegal position after one of the delete_chars*
   1584    functions, but calling get_legal_normal_pos also would be weird because it
   1585    wouldn't necessarily be at the deletion index anymore */
   1586 #define GEN_DELETE_FUNCS(type, next_func, prev_func)                                                       \
   1587 static struct action                                                                                       \
   1588 delete_##type##_backwards_base(ledit_view *view, char *text, size_t len, int multiline) {                  \
   1589 	(void)text;                                                                                        \
   1590 	(void)len;                                                                                         \
   1591 	int num = get_key_repeat();                                                                        \
   1592 	if (num == -1) {                                                                                   \
   1593 		window_show_message(view->window, "Invalid key", -1);                                      \
   1594 		return (struct action){ACTION_NONE, NULL};                                                 \
   1595 	} else if (num == 0) {                                                                             \
   1596 		num = 1;                                                                                   \
   1597 	}                                                                                                  \
   1598 	size_t start_line, start_index;                                                                    \
   1599 	prev_func(                                                                                         \
   1600 	    view, view->cur_line, view->cur_index,                                                         \
   1601 	    num, multiline, &start_line, &start_index                                                      \
   1602 	);                                                                                                 \
   1603 	delete_range(                                                                                      \
   1604 	    view, 0, 0,                                                                                    \
   1605 	    start_line, start_index,                                                                       \
   1606 	    view->cur_line, view->cur_index, view->mode != INSERT                                          \
   1607 	);                                                                                                 \
   1608 	view->cur_line = start_line;                                                                       \
   1609 	view->cur_index = start_index;                                                                     \
   1610 	if (view->mode == NORMAL)                                                                          \
   1611 		view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);                         \
   1612 	if (view->mode != INSERT) {                                                                        \
   1613 		paste_buffer_line_based = 0;                                                               \
   1614 		finalize_repetition_stack();                                                               \
   1615 	}                                                                                                  \
   1616 	return (struct action){ACTION_NONE, NULL};                                                         \
   1617 }                                                                                                          \
   1618                                                                                                            \
   1619 static struct action                                                                                       \
   1620 delete_##type##_backwards(ledit_view *view, char *text, size_t len) {                                      \
   1621 	return delete_##type##_backwards_base(view, text, len, 0);                                         \
   1622 }                                                                                                          \
   1623                                                                                                            \
   1624 static struct action                                                                                       \
   1625 delete_##type##_backwards_multiline(ledit_view *view, char *text, size_t len) {                            \
   1626 	return delete_##type##_backwards_base(view, text, len, 1);                                         \
   1627 }                                                                                                          \
   1628                                                                                                            \
   1629 static struct action                                                                                       \
   1630 delete_##type##_forwards_base(ledit_view *view, char *text, size_t len, int multiline) {                   \
   1631 	(void)text;                                                                                        \
   1632 	(void)len;                                                                                         \
   1633 	int num = get_key_repeat();                                                                        \
   1634 	if (num == -1) {                                                                                   \
   1635 		window_show_message(view->window, "Invalid key", -1);                                      \
   1636 		return (struct action){ACTION_NONE, NULL};                                                 \
   1637 	} else if (num == 0) {                                                                             \
   1638 		num = 1;                                                                                   \
   1639 	}                                                                                                  \
   1640 	size_t end_line, end_index;                                                                        \
   1641 	next_func(                                                                                         \
   1642 	    view, view->cur_line, view->cur_index,                                                         \
   1643 	    num, multiline, &end_line, &end_index                                                          \
   1644 	);                                                                                                 \
   1645 	delete_range(                                                                                      \
   1646 	    view, 0, 0,                                                                                    \
   1647 	    view->cur_line, view->cur_index,                                                               \
   1648 	    end_line, end_index, view->mode != INSERT                                                      \
   1649 	);                                                                                                 \
   1650 	if (view->mode == NORMAL)                                                                          \
   1651 		view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);                         \
   1652 	if (view->mode != INSERT) {                                                                        \
   1653 		paste_buffer_line_based = 0;                                                               \
   1654 		finalize_repetition_stack();                                                               \
   1655 	}                                                                                                  \
   1656 	return (struct action){ACTION_NONE, NULL};                                                         \
   1657 }                                                                                                          \
   1658                                                                                                            \
   1659 static struct action                                                                                       \
   1660 delete_##type##_forwards(ledit_view *view, char *text, size_t len) {                                       \
   1661 	return delete_##type##_forwards_base(view, text, len, 0);                                          \
   1662 }                                                                                                          \
   1663                                                                                                            \
   1664 static struct action                                                                                       \
   1665 delete_##type##_forwards_multiline(ledit_view *view, char *text, size_t len) {                             \
   1666 	return delete_##type##_forwards_base(view, text, len, 1);                                          \
   1667 }
   1668 
   1669 /* Yes, I know, all these helpers are ugly... */
   1670 #define buffer_next_char_pos_helper(view, line, byte, num, multiline, line_ret, byte_ret) \
   1671     buffer_next_char_pos((view)->buffer, line, byte, num, multiline, line_ret, byte_ret)
   1672 #define buffer_prev_char_pos_helper(view, line, byte, num, multiline, line_ret, byte_ret) \
   1673     buffer_prev_char_pos((view)->buffer, line, byte, num, multiline, line_ret, byte_ret)
   1674 
   1675 GEN_DELETE_FUNCS(graphemes, view_next_cursor_pos, view_prev_cursor_pos)
   1676 GEN_DELETE_FUNCS(chars, buffer_next_char_pos_helper, buffer_prev_char_pos_helper)
   1677 
   1678 static struct action
   1679 move_to_eol(ledit_view *view, char *text, size_t len) {
   1680 	(void)text;
   1681 	(void)len;
   1682 	motion_callback cb;
   1683 	int num = get_key_repeat_and_motion_cb(view, &cb);
   1684 	if (num == -1)
   1685 		return err_invalid_key(view);
   1686 	if (num == 0)
   1687 		num = 1;
   1688 	view_wipe_line_cursor_attrs(view, view->cur_line);
   1689 	size_t new_line;
   1690 	int new_softline;
   1691 	get_new_line_softline(
   1692 	    view, view->cur_line, view->cur_index, num - 1,
   1693 	    &new_line, &new_softline
   1694 	);
   1695 	ledit_line *ll = buffer_get_line(view->buffer, new_line);
   1696 	size_t end_index = ll->len;
   1697 	if (!view->buffer->hard_line_based) {
   1698 		size_t tmp;
   1699 		view_get_softline_bounds(view, new_line, new_softline, &tmp, &end_index);
   1700 	}
   1701 	if (cb != NULL) {
   1702 		cb(view, new_line, end_index, KEY_MOTION_CHAR);
   1703 	} else {
   1704 		view->cur_line = new_line;
   1705 		view->cur_index = end_index;
   1706 		if (view->mode == VISUAL) {
   1707 			view_set_selection(
   1708 			    view,
   1709 			    view->sel.line1, view->sel.byte1,
   1710 			    new_line, end_index
   1711 			);
   1712 		} else if (view->mode == NORMAL) {
   1713 			/* FIXME: this is weird because the cursor is actually on the
   1714 			   next soft line, but the alternative has too many weird effects
   1715 			   with bidi text */
   1716 			view->cur_index = view_get_legal_normal_pos(
   1717 			    view, view->cur_line, view->cur_index
   1718 			);
   1719 			view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
   1720 		}
   1721 	}
   1722 	return (struct action){ACTION_NONE, NULL};
   1723 }
   1724 
   1725 #define GEN_WORD_MOVEMENT(name, func)                                            \
   1726 static struct action                                                             \
   1727 name(ledit_view *view, char *text, size_t len) {                                 \
   1728 	(void)text;                                                              \
   1729 	(void)len;                                                               \
   1730 	motion_callback cb;                                                      \
   1731 	int num = get_key_repeat_and_motion_cb(view, &cb);                       \
   1732 	if (num == -1)                                                           \
   1733 		return err_invalid_key(view);                                    \
   1734 	if (num == 0)                                                            \
   1735 		num = 1;                                                         \
   1736 	size_t new_line, new_index, new_real_index;                              \
   1737 	func(                                                                    \
   1738 	    view,                                                                \
   1739 	    view->cur_line, view->cur_index, num,                                \
   1740 	    &new_line, &new_index, &new_real_index                               \
   1741 	);                                                                       \
   1742 	if (cb != NULL) {                                                        \
   1743 		cb(view, new_line, new_real_index, KEY_MOTION_CHAR);             \
   1744 	} else {                                                                 \
   1745 		if (view->mode == VISUAL) {                                      \
   1746 			view_set_selection(                                      \
   1747 			    view,                                                \
   1748 			    view->sel.line1, view->sel.byte1,                    \
   1749 			    new_line, new_real_index                             \
   1750 			);                                                       \
   1751 			view->cur_line = new_line;                               \
   1752 			view->cur_index = new_real_index;                        \
   1753 		} else {                                                         \
   1754 			if (new_line != view->cur_line)                          \
   1755 				view_wipe_line_cursor_attrs(                     \
   1756 				    view, view->cur_line                         \
   1757 				);                                               \
   1758 			view->cur_line = new_line;                               \
   1759 			view->cur_index = new_index;                             \
   1760 			if (view->mode == NORMAL) {                              \
   1761 				view_set_line_cursor_attrs(                      \
   1762 				    view, view->cur_line, view->cur_index        \
   1763 				);                                               \
   1764 			}                                                        \
   1765 		}                                                                \
   1766 		discard_repetition_stack();                                      \
   1767 	}                                                                        \
   1768 	clear_key_stack();                                                       \
   1769 	return (struct action){ACTION_NONE, NULL};                               \
   1770 }
   1771 
   1772 GEN_WORD_MOVEMENT(next_word, view_next_word)
   1773 GEN_WORD_MOVEMENT(next_word_end, view_next_word_end)
   1774 GEN_WORD_MOVEMENT(next_bigword, view_next_bigword)
   1775 GEN_WORD_MOVEMENT(next_bigword_end, view_next_bigword_end)
   1776 GEN_WORD_MOVEMENT(prev_word, view_prev_word)
   1777 GEN_WORD_MOVEMENT(prev_bigword, view_prev_bigword)
   1778 
   1779 static void
   1780 move_cursor_left_right(ledit_view *view, int dir, int allow_illegal_index) {
   1781 	motion_callback cb;
   1782 	int num = get_key_repeat_and_motion_cb(view, &cb);
   1783 	if (num == -1)
   1784 		(void)err_invalid_key(view);
   1785 	if (num == 0)
   1786 		num = 1;
   1787 
   1788 	ledit_line *cur_line = buffer_get_line(view->buffer, view->cur_line);
   1789 	/* FIXME: standardize interface - num * dir or separately? */
   1790 	size_t last_index;
   1791 	size_t new_index = view_move_cursor_visually(
   1792 	    view, view->cur_line, view->cur_index, num * dir, &last_index
   1793 	);
   1794 	/* when in normal mode, the cursor cannot be at the very end
   1795 	   of the line because it's always covering a character */
   1796 	if (new_index >= cur_line->len) {
   1797 		if (!allow_illegal_index &&
   1798 		    view->mode == NORMAL && cb == NULL) {
   1799 			new_index = last_index;
   1800 		} else {
   1801 			/* FIXME: I guess this is unnecessary */
   1802 			new_index = cur_line->len;
   1803 		}
   1804 	}
   1805 	if (cb != NULL) {
   1806 		cb(view, view->cur_line, new_index, KEY_MOTION_CHAR);
   1807 	} else {
   1808 		view->cur_index = new_index;
   1809 		if (view->mode == VISUAL) {
   1810 			/* FIXME: check if view->sel_valid and only use it then (also change in other places) */
   1811 			view_set_selection(view, view->sel.line1, view->sel.byte1, view->cur_line, new_index);
   1812 		} else if (view->mode == INSERT && view->sel_valid) {
   1813 			/* FIXME: I guess this is unnecessary now that no
   1814 			   selection is allowed in insert mode */
   1815 			view_wipe_selection(view);
   1816 		} else if (view->mode == NORMAL) {
   1817 			view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
   1818 		}
   1819 		view->redraw = 1;
   1820 		discard_repetition_stack();
   1821 	}
   1822 	clear_key_stack();
   1823 }
   1824 
   1825 static struct action
   1826 cursor_left(ledit_view *view, char *text, size_t len) {
   1827 	(void)text;
   1828 	(void)len;
   1829 	move_cursor_left_right(view, -1, 0);
   1830 	return (struct action){ACTION_NONE, NULL};
   1831 }
   1832 
   1833 static struct action
   1834 cursor_right(ledit_view *view, char *text, size_t len) {
   1835 	(void)text;
   1836 	(void)len;
   1837 	move_cursor_left_right(view, 1, 0);
   1838 	return (struct action){ACTION_NONE, NULL};
   1839 }
   1840 
   1841 static struct action
   1842 break_line(ledit_view *view, char *text, size_t len) {
   1843 	(void)text;
   1844 	(void)len;
   1845 	int start_group = 1;
   1846 	/* FIXME: this is unnecessary now because no selection is supported in insert mode */
   1847 	if (delete_selection(view))
   1848 		start_group = 0;
   1849 	if (view->mode == NORMAL)
   1850 		view_wipe_line_cursor_attrs(view, view->cur_line);
   1851 	insert_text(view, view->cur_line, view->cur_index, "\n", 1, 0, 0, 0, 0, 0, 0, start_group);
   1852 	if (view->mode == NORMAL) {
   1853 		view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view->cur_index);
   1854 		view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
   1855 	}
   1856 	return (struct action){ACTION_NONE, NULL};
   1857 }
   1858 
   1859 static void
   1860 move_cursor_logically(ledit_view *view, int movement_dir, int allow_illegal_index) {
   1861 	if (movement_dir < 0) {
   1862 		view_prev_cursor_pos(
   1863 		    view, view->cur_line, view->cur_index, 1, 0, NULL, &view->cur_index
   1864 		);
   1865 	} else {
   1866 		view_next_cursor_pos(
   1867 		    view, view->cur_line, view->cur_index, 1, 0, NULL, &view->cur_index
   1868 		);
   1869 	}
   1870 	if (!allow_illegal_index) {
   1871 		view->cur_index = view_get_legal_normal_pos(
   1872 		    view, view->cur_line, view->cur_index
   1873 		);
   1874 	}
   1875 }
   1876 
   1877 static struct action
   1878 return_to_normal(ledit_view *view, char *text, size_t len) {
   1879 	(void)text;
   1880 	(void)len;
   1881 	clear_key_stack();
   1882 	if (view->mode == INSERT)
   1883 		finalize_repetition_stack();
   1884 	/* FIXME: I guess this is unnecessary now that insert mode does not support selection */
   1885 	if (view->mode == INSERT && view->sel_valid) {
   1886 		view_set_mode(view, VISUAL);
   1887 	} else if (view->mode != NORMAL) {
   1888 		view_set_mode(view, NORMAL);
   1889 		move_cursor_logically(view, -1, 0);
   1890 		view_wipe_selection(view);
   1891 		view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
   1892 	}
   1893 	return (struct action){ACTION_NONE, NULL};
   1894 }
   1895 
   1896 static struct action
   1897 enter_insert(ledit_view *view, char *text, size_t len) {
   1898 	(void)text;
   1899 	(void)len;
   1900 	if (view->mode == VISUAL) {
   1901 		view_wipe_selection(view);
   1902 	}
   1903 	view_wipe_line_cursor_attrs(view, view->cur_line);
   1904 	view_set_mode(view, INSERT);
   1905 	clear_key_stack();
   1906 	return (struct action){ACTION_NONE, NULL};
   1907 }
   1908 
   1909 /* FIXME: Check if previous key allows motion command - or should this be checked automatically before? */
   1910 static void
   1911 move_cursor_up_down(ledit_view *view, int dir) {
   1912 	size_t new_line;
   1913 	int new_softline;
   1914 
   1915 	motion_callback cb;
   1916 	int num = get_key_repeat_and_motion_cb(view, &cb);
   1917 	if (num == -1)
   1918 		(void)err_invalid_key(view);
   1919 	if (num == 0)
   1920 		num = 1;
   1921 	num *= dir;
   1922 
   1923 	get_new_line_softline(
   1924 	    view, view->cur_line, view->cur_index,
   1925 	    num, &new_line, &new_softline
   1926 	);
   1927 
   1928 	if (cb != NULL) {
   1929 		size_t start, end;
   1930 		view_get_softline_bounds(view, new_line, new_softline, &start, &end);
   1931 		cb(view, new_line, start, KEY_MOTION_LINE);
   1932 	} else {
   1933 		int lineno, x, diff = 0, old_line = view->cur_line;
   1934 		view_pos_to_x_softline(view, view->cur_line, view->cur_index, &x, &lineno);
   1935 		view->cur_index = view_x_softline_to_pos(view, new_line, x, new_softline);
   1936 		if (view->cur_line != new_line)
   1937 			diff = 1;
   1938 		view->cur_line = new_line;
   1939 
   1940 		if (view->mode == VISUAL) {
   1941 			view_set_selection(view, view->sel.line1, view->sel.byte1, view->cur_line, view->cur_index);
   1942 		} else if (view->mode == NORMAL) {
   1943 			if (diff)
   1944 				view_wipe_line_cursor_attrs(view, old_line);
   1945 			view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
   1946 		}
   1947 		view->redraw = 1;
   1948 		discard_repetition_stack();
   1949 	}
   1950 	clear_key_stack();
   1951 }
   1952 
   1953 static struct action
   1954 cursor_down(ledit_view *view, char *text, size_t len) {
   1955 	(void)text;
   1956 	(void)len;
   1957 	move_cursor_up_down(view, 1);
   1958 	return (struct action){ACTION_NONE, NULL};
   1959 }
   1960 
   1961 static struct action
   1962 cursor_up(ledit_view *view, char *text, size_t len) {
   1963 	(void)text;
   1964 	(void)len;
   1965 	move_cursor_up_down(view, -1);
   1966 	return (struct action){ACTION_NONE, NULL};
   1967 }
   1968 
   1969 static struct action
   1970 join_lines(ledit_view *view, char *text, size_t len) {
   1971 	(void)text;
   1972 	(void)len;
   1973 	int num = get_key_repeat();
   1974 	if (num == -1)
   1975 		return err_invalid_key(view);
   1976 	if (num == 0)
   1977 		num = 1;
   1978 	int start_group = 1;
   1979 	ledit_line *ll1;
   1980 	ledit_line *ll2;
   1981 	size_t cur_line = view->cur_line;
   1982 	/* don't return yet so the stuff at the bottom gets called,
   1983 	   in particular finalize_repetition_stack */
   1984 	if (cur_line == view->lines_num - 1)
   1985 		window_show_message(view->window, "No following lines to join", -1);
   1986 	for (int i = 0; i < num; i++) {
   1987 		if (cur_line == view->lines_num - 1)
   1988 			break;
   1989 		ll1 = buffer_get_line(view->buffer, cur_line);
   1990 		ll2 = buffer_get_line(view->buffer, cur_line + 1);
   1991 		/* figure out if the current line ends in whitespace -
   1992 		   this could probably be improved */
   1993 		size_t last_char_byte = line_prev_utf8(ll1, ll1->len);
   1994 		size_t last_ws = view_line_next_non_whitespace(view, cur_line, last_char_byte);
   1995 		int end_in_ws = (last_ws == ll1->len); /* also works if ll1->len == 0 */
   1996 		size_t start_idx = view_line_next_non_whitespace(view, cur_line + 1, 0);
   1997 		/* save len here because view_delete_range_base calls view_get_legal_normal_pos,
   1998 		   so the returned index may not be right for the following space insertion */
   1999 		/* although, on second thought, that only happens when the next line is empty,
   2000 		   which is a special case that is ignored below... */
   2001 		size_t len = ll1->len;
   2002 		size_t len2 = ll2->len;
   2003 		view_delete_range_base(
   2004 		    view, DELETE_CHAR, start_group,
   2005 		    cur_line, ll1->len, cur_line + 1, start_idx,
   2006 		    &view->cur_line, &view->cur_index, NULL
   2007 		);
   2008 		/* insert space if there is no other whitespace */
   2009 		if (!end_in_ws && len2 > 0) {
   2010 			ledit_range cur_range = {.line1 = view->cur_line, .byte1 = view->cur_index, .line2 = 0, .byte2 = 0};
   2011 			buffer_insert_with_undo_base(
   2012 			    view->buffer, cur_range, 1, 0,
   2013 			    view->mode, cur_line, len,
   2014 			    " ", 1, &view->cur_line, &view->cur_index
   2015 			);
   2016 		}
   2017 		start_group = 0;
   2018 	}
   2019 	buffer_recalc_all_views_from_line(view->buffer, cur_line);
   2020 	/* FIXME: should view_set_line_cursor_attrs just have this check included? */
   2021 	if (view->mode == NORMAL) {
   2022 		view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view->cur_index);
   2023 		view_set_line_cursor_attrs(
   2024 		    view, view->cur_line, view->cur_index
   2025 		);
   2026 	}
   2027 	if (view->mode != INSERT)
   2028 		finalize_repetition_stack();
   2029 	return (struct action){ACTION_NONE, NULL};
   2030 }
   2031 
   2032 static struct action
   2033 insert_at_beginning(ledit_view *view, char *text, size_t len) {
   2034 	if (!key_stack_empty())
   2035 		return err_invalid_key(view);
   2036 	enter_insert(view, text, len);
   2037 	size_t new_index = 0;
   2038 	if (!view->buffer->hard_line_based) {
   2039 		size_t tmp;
   2040 		view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &new_index, &tmp);
   2041 	}
   2042 	push_undo_empty_insert(view, view->cur_line, view->cur_index, 1);
   2043 	view->cur_index = new_index;
   2044 	view_wipe_line_cursor_attrs(view, view->cur_line);
   2045 	return (struct action){ACTION_NONE, NULL};
   2046 }
   2047 
   2048 static struct action
   2049 cursor_to_first_non_ws(ledit_view *view, char *text, size_t len) {
   2050 	(void)text;
   2051 	(void)len;
   2052 	motion_callback cb;
   2053 	int num = get_key_repeat_and_motion_cb(view, &cb);
   2054 	if (num != 0)
   2055 		return err_invalid_key(view);
   2056 	size_t new_index = 0;
   2057 	if (view->buffer->hard_line_based) {
   2058 		new_index = view_line_next_non_whitespace(view, view->cur_line, 0);
   2059 	} else {
   2060 		size_t start, end;
   2061 		view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start, &end);
   2062 		new_index = view_line_next_non_whitespace(view, view->cur_line, start);
   2063 		/* next non-whitespace might be on next softline */
   2064 		if (new_index >= end) {
   2065 			view_prev_cursor_pos(
   2066 			    view, view->cur_line, end, 1, 0, NULL, &new_index
   2067 			);
   2068 		}
   2069 	}
   2070 	if (cb != NULL) {
   2071 		cb(view, view->cur_line, new_index, KEY_MOTION_CHAR);
   2072 	} else {
   2073 		view->cur_index = new_index;
   2074 		if (view->mode == VISUAL) {
   2075 			view_set_selection(
   2076 			    view,
   2077 			    view->sel.line1, view->sel.byte1,
   2078 			    view->cur_line, view->cur_index
   2079 			);
   2080 		} else if (view->mode == NORMAL) {
   2081 			view_set_line_cursor_attrs(
   2082 			    view, view->cur_line, view->cur_index
   2083 			);
   2084 		}
   2085 		discard_repetition_stack();
   2086 	}
   2087 	return (struct action){ACTION_NONE, NULL};
   2088 }
   2089 
   2090 static struct action
   2091 cursor_to_beginning(ledit_view *view, char *text, size_t len) {
   2092 	(void)text;
   2093 	(void)len;
   2094 	motion_callback cb;
   2095 	int num = get_key_repeat_and_motion_cb(view, &cb);
   2096 	if (num != 0)
   2097 		return err_invalid_key(view);
   2098 	/* FIXME: should anything be done with num? */
   2099 	size_t start_index = 0;
   2100 	if (!view->buffer->hard_line_based) {
   2101 		size_t tmp;
   2102 		view_get_pos_softline_bounds(view, view->cur_line, view->cur_index, &start_index, &tmp);
   2103 	}
   2104 	if (cb != NULL) {
   2105 		cb(view, view->cur_line, start_index, KEY_MOTION_CHAR);
   2106 	} else {
   2107 		view->cur_index = start_index;
   2108 		if (view->mode == VISUAL) {
   2109 			view_set_selection(
   2110 			    view,
   2111 			    view->sel.line1, view->sel.byte1,
   2112 			    view->cur_line, view->cur_index
   2113 			);
   2114 		} else if (view->mode == NORMAL) {
   2115 			view_set_line_cursor_attrs(
   2116 			    view, view->cur_line, view->cur_index
   2117 			);
   2118 		}
   2119 		discard_repetition_stack();
   2120 	}
   2121 	clear_key_stack();
   2122 	return (struct action){ACTION_NONE, NULL};
   2123 }
   2124 
   2125 static struct action
   2126 enter_visual(ledit_view *view, char *text, size_t len) {
   2127 	(void)text;
   2128 	(void)len;
   2129 	view_set_mode(view, VISUAL);
   2130 	/* FIXME: set view->sel_valid? */
   2131 	view->sel.line1 = view->sel.line2 = view->cur_line;
   2132 	view->sel.byte1 = view->sel.byte2 = view->cur_index;
   2133 	view_wipe_line_cursor_attrs(view, view->cur_line);
   2134 	clear_key_stack(); /* FIXME: error if not empty? */
   2135 	return (struct action){ACTION_NONE, NULL};
   2136 }
   2137 
   2138 static struct action
   2139 switch_selection_end(ledit_view *view, char *text, size_t len) {
   2140 	(void)text;
   2141 	(void)len;
   2142 	swap_sz(&view->sel.line1, &view->sel.line2);
   2143 	swap_sz(&view->sel.byte1, &view->sel.byte2);
   2144 	view->cur_line = view->sel.line2;
   2145 	view->cur_index = view->sel.byte2;
   2146 	return (struct action){ACTION_NONE, NULL};
   2147 }
   2148 
   2149 static struct action
   2150 enter_commandedit(ledit_view *view, char *text, size_t len) {
   2151 	(void)text;
   2152 	(void)len;
   2153 	/* FIXME: wipe selection? */
   2154 	char *str = view->sel_valid ? ":'<,'>" : ":";
   2155 	window_set_bottom_bar_text(view->window, str, -1);
   2156 	window_set_bottom_bar_cursor(view->window, strlen(str));
   2157 	window_set_bottom_bar_min_pos(view->window, 1);
   2158 	view->cur_command_type = CMD_EDIT;
   2159 	window_set_bottom_bar_text_shown(view->window, 1);
   2160 	discard_repetition_stack();
   2161 	return (struct action){ACTION_GRABKEY, &command_key_handler};
   2162 }
   2163 
   2164 /* FIXME: support visual mode - maybe change selection to new position
   2165    or at least support only searching within the range given by the
   2166    selection */
   2167 static struct action
   2168 enter_searchedit_forward(ledit_view *view, char *text, size_t len) {
   2169 	(void)text;
   2170 	(void)len;
   2171 	window_set_bottom_bar_text(view->window, "/", -1);
   2172 	window_set_bottom_bar_min_pos(view->window, 1);
   2173 	window_set_bottom_bar_cursor(view->window, 1);
   2174 	view->cur_command_type = CMD_EDITSEARCH;
   2175 	window_set_bottom_bar_text_shown(view->window, 1);
   2176 	discard_repetition_stack();
   2177 	return (struct action){ACTION_GRABKEY, &command_key_handler};
   2178 }
   2179 
   2180 static struct action
   2181 enter_searchedit_backward(ledit_view *view, char *text, size_t len) {
   2182 	(void)text;
   2183 	(void)len;
   2184 	window_set_bottom_bar_text(view->window, "?", -1);
   2185 	window_set_bottom_bar_min_pos(view->window, 1);
   2186 	window_set_bottom_bar_cursor(view->window, 1);
   2187 	view->cur_command_type = CMD_EDITSEARCHB;
   2188 	window_set_bottom_bar_text_shown(view->window, 1);
   2189 	discard_repetition_stack();
   2190 	return (struct action){ACTION_GRABKEY, &command_key_handler};
   2191 }
   2192 
   2193 /* FIXME: differentiate between jumping to line and index like nvi */
   2194 static struct action
   2195 insert_mark_cb(ledit_view *view, char *text, size_t len) {
   2196 	grab_char_cb = NULL;
   2197 	buffer_insert_mark(
   2198 	    view->buffer, text, len, view->cur_line, view->cur_index
   2199 	);
   2200 	return (struct action){ACTION_NONE, NULL};
   2201 }
   2202 
   2203 static struct action
   2204 jump_to_mark_cb(ledit_view *view, char *text, size_t len) {
   2205 	grab_char_cb = NULL;
   2206 	motion_callback cb;
   2207 	int num = get_key_repeat_and_motion_cb(view, &cb);
   2208 	if (num > 0)
   2209 		return err_invalid_key(view);
   2210 	size_t line = 0, index = 0;
   2211 	/* FIXME: better error */
   2212 	if (buffer_get_mark(view->buffer, text, len, &line, &index))
   2213 		return err_invalid_key(view);
   2214 	if (view->mode == VISUAL) {
   2215 		view_set_selection(
   2216 		    view, view->sel.line1, view->sel.byte1, line, index
   2217 		);
   2218 		view->cur_line = line;
   2219 		view->cur_index = index;
   2220 	} else {
   2221 		if (cb) {
   2222 			cb(view, line, index, KEY_MOTION_LINE);
   2223 		} else {
   2224 			view_wipe_line_cursor_attrs(view, view->cur_line);
   2225 			view->cur_line = line;
   2226 			view->cur_index = index;
   2227 			if (view->mode == NORMAL) {
   2228 				view->cur_index = view_get_legal_normal_pos(
   2229 				    view, view->cur_line, view->cur_index
   2230 				);
   2231 				view_set_line_cursor_attrs(
   2232 				    view, view->cur_line, view->cur_index
   2233 				);
   2234 			}
   2235 			discard_repetition_stack();
   2236 		}
   2237 	}
   2238 	return (struct action){ACTION_NONE, NULL};
   2239 }
   2240 
   2241 static struct action
   2242 insert_mark(ledit_view *view, char *text, size_t len) {
   2243 	(void)view;
   2244 	(void)text;
   2245 	(void)len;
   2246 	grab_char_cb = &insert_mark_cb;
   2247 	discard_repetition_stack();
   2248 	return (struct action){ACTION_NONE, NULL};
   2249 }
   2250 
   2251 static struct action
   2252 jump_to_mark(ledit_view *view, char *text, size_t len) {
   2253 	(void)view;
   2254 	(void)text;
   2255 	(void)len;
   2256 	grab_char_cb = &jump_to_mark_cb;
   2257 	/* FIXME: should it be discarded here? */
   2258 	discard_repetition_stack();
   2259 	return (struct action){ACTION_NONE, NULL};
   2260 }
   2261 
   2262 /* FIXME: support visual mode, i.e. change selection to new place? */
   2263 static struct action
   2264 key_search_next(ledit_view *view, char *text, size_t len) {
   2265 	(void)text;
   2266 	(void)len;
   2267 	search_next(view);
   2268 	discard_repetition_stack();
   2269 	return (struct action){ACTION_NONE, NULL};
   2270 }
   2271 
   2272 static struct action
   2273 key_search_prev(ledit_view *view, char *text, size_t len) {
   2274 	(void)text;
   2275 	(void)len;
   2276 	search_prev(view);
   2277 	discard_repetition_stack();
   2278 	return (struct action){ACTION_NONE, NULL};
   2279 }
   2280 
   2281 static struct action
   2282 show_line(ledit_view *view, char *text, size_t len) {
   2283 	(void)text;
   2284 	(void)len;
   2285 	window_show_message_fmt(
   2286 	    view->window,
   2287 	    "%s: %s: line %zu of %zu",
   2288 	    view->buffer->filename ? view->buffer->filename : "(no filename)",
   2289 	    view->buffer->modified ? "modified" : "unmodified",
   2290 	    add_sz(view->cur_line, 1), view->lines_num
   2291 	);
   2292 	discard_repetition_stack();
   2293 	return (struct action){ACTION_NONE, NULL};
   2294 }
   2295 
   2296 static struct action
   2297 undo(ledit_view *view, char *text, size_t len) {
   2298 	(void)text;
   2299 	(void)len;
   2300 	int num = get_key_repeat();
   2301 	if (num == -1)
   2302 		return err_invalid_key(view);
   2303 	if (num == 0)
   2304 		num = 1;
   2305 	view_wipe_selection(view);
   2306 	view_undo(view, num);
   2307 	if (view->mode != INSERT)
   2308 		finalize_repetition_stack();
   2309 	return (struct action){ACTION_NONE, NULL};
   2310 }
   2311 
   2312 static struct action
   2313 redo(ledit_view *view, char *text, size_t len) {
   2314 	(void)text;
   2315 	(void)len;
   2316 	int num = get_key_repeat();
   2317 	if (num == -1)
   2318 		return err_invalid_key(view);
   2319 	if (num == 0)
   2320 		num = 1;
   2321 	view_wipe_selection(view);
   2322 	view_redo(view, num);
   2323 	if (view->mode != INSERT)
   2324 		finalize_repetition_stack();
   2325 	return (struct action){ACTION_NONE, NULL};
   2326 }
   2327 
   2328 static struct action
   2329 insert_mode_insert_text(ledit_view *view, char *text, size_t len) {
   2330 	if (!key_stack_empty())
   2331 		return err_invalid_key(view);
   2332 	/* this shouldn't be necessary */
   2333 	delete_selection(view);
   2334 	insert_text(view, view->cur_line, view->cur_index, text, len, 0, 0, 0, 0, 0, 0, 1);
   2335 	return (struct action){ACTION_NONE, NULL};
   2336 }
   2337 
   2338 static struct action
   2339 clipcopy(ledit_view *view, char *text, size_t len) {
   2340 	(void)text;
   2341 	(void)len;
   2342 	if (!key_stack_empty())
   2343 		return err_invalid_key(view);
   2344 	/* FIXME: abstract this through view */
   2345 	clipboard_primary_to_clipboard(view->buffer->clipboard);
   2346 	discard_repetition_stack();
   2347 	return (struct action){ACTION_NONE, NULL};
   2348 }
   2349 
   2350 static struct action
   2351 clippaste(ledit_view *view, char *text, size_t len) {
   2352 	(void)text;
   2353 	(void)len;
   2354 	if (!key_stack_empty())
   2355 		return err_invalid_key(view);
   2356 	/* FIXME: the selection deletion and pasting should be in the same undo group */
   2357 	if (view->mode == VISUAL) {
   2358 		/* Note; this sets the current position */
   2359 		delete_selection(view);
   2360 	}
   2361 	view_paste_clipboard(view);
   2362 	if (view->mode != INSERT)
   2363 		finalize_repetition_stack();
   2364 	return (struct action){ACTION_NONE, NULL};
   2365 }
   2366 
   2367 /* FIXME: make sure the found position is valid cursor position? */
   2368 static int
   2369 search_str_backwards(char *haystack, size_t hlen, char *needle, size_t nlen, size_t start_index, size_t *ret) {
   2370 	if (start_index > hlen)
   2371 		return -1;
   2372 	size_t new_index = start_index;
   2373 	for (; new_index > 0; new_index--) {
   2374 		if (!strncmp(haystack + new_index - 1, needle, nlen)) {
   2375 			*ret = new_index - 1;
   2376 			return 0;
   2377 		}
   2378 	}
   2379 	return -1;
   2380 }
   2381 
   2382 static int
   2383 search_str_forwards(char *haystack, size_t hlen, char *needle, size_t nlen, size_t start_index, size_t *ret) {
   2384 	if (start_index >= hlen)
   2385 		return -1;
   2386 	/* duplicate so it is nul-terminated */
   2387 	char *search_str = ledit_strndup(needle, nlen);
   2388 	char *res = strstr(haystack + start_index + 1, search_str);
   2389 	free(search_str);
   2390 	/* FIXME: is this legal? */
   2391 	if (res) {
   2392 		*ret = (size_t)(res - haystack);
   2393 		return 0;
   2394 	} else {
   2395 		return -1;
   2396 	}
   2397 }
   2398 
   2399 /* just to make the macro below work for all cases */
   2400 /* FIXME: is there a more elegant way to do this? */
   2401 static void
   2402 dummy_cursor_helper(
   2403     ledit_view *view, size_t line, size_t byte,
   2404     int num, int multiline, size_t *line_ret, size_t *byte_ret) {
   2405 	(void)view; (void)num; (void)multiline;
   2406 	if (line_ret)
   2407 		*line_ret = line;
   2408 	if (byte_ret)
   2409 		*byte_ret = byte;
   2410 }
   2411 
   2412 /* FIXME: add checks to functions that current mode is supported */
   2413 
   2414 /* name is the name of the generated pair of functions
   2415    search_func is used to get the next index (possibly called
   2416    repeatedly if there is a repeat number on the key stack)
   2417    funcm = func motion, funcn = func normal, funcv = func visual
   2418    -> these are called to modify the index returned by search_func
   2419       cur_funcm is called to get the index for a motion callback
   2420       cur_funcn is called to position the cursor in normal and insert mode
   2421       cur_funcv is called to position the cursor in visual mode */
   2422 #define GEN_MOVE_TO_CHAR(name, search_func, cur_funcm, cur_funcn, cur_funcv)       \
   2423 static struct action                                                               \
   2424 name##_cb(ledit_view *view, char *text, size_t len) {                              \
   2425 	motion_callback cb = NULL;                                                 \
   2426 	int num = get_key_repeat_and_motion_cb(view, &cb);                         \
   2427 	if (num == -1)                                                             \
   2428 		return err_invalid_key(view);                                      \
   2429 	if (num == 0)                                                              \
   2430 		num = 1;                                                           \
   2431 	ledit_line *ll = buffer_get_line(view->buffer, view->cur_line);            \
   2432 	size_t new_index = view->cur_index;                                        \
   2433 	int ret = -1;                                                              \
   2434 	for (int i = 0; i < num; i++) {                                            \
   2435 		if ((ret = search_func(                                            \
   2436 		     ll->text, ll->len, text, len,                                 \
   2437 		     new_index, &new_index)) == -1) {                              \
   2438 			break;                                                     \
   2439 		}                                                                  \
   2440 	}                                                                          \
   2441 	if (!ret) {                                                                \
   2442 		if (cb != NULL) {                                                  \
   2443 			cur_funcm(                                                 \
   2444 			    view, view->cur_line, new_index,                       \
   2445 			    1, 0, NULL, &new_index                                 \
   2446 			);                                                         \
   2447 			cb(view, view->cur_line, new_index, KEY_MOTION_CHAR);      \
   2448 		} else {                                                           \
   2449 			if (view->mode == VISUAL) {                                \
   2450 				cur_funcv(                                         \
   2451 				    view, view->cur_line, new_index,               \
   2452 				    1, 0, NULL, &view->cur_index                   \
   2453 				);                                                 \
   2454 				view_set_selection(                                \
   2455 				    view,                                          \
   2456 				    view->sel.line1, view->sel.byte1,              \
   2457 				    view->cur_line, view->cur_index                \
   2458 				);                                                 \
   2459 			} else {                                                   \
   2460 				cur_funcn(                                         \
   2461 				    view, view->cur_line, new_index,               \
   2462 				    1, 0, NULL, &view->cur_index                   \
   2463 				);                                                 \
   2464 				if (view->mode == NORMAL) {                        \
   2465 					view_set_line_cursor_attrs(                \
   2466 					    view, view->cur_line, view->cur_index  \
   2467 					);                                         \
   2468 				}                                                  \
   2469 			}                                                          \
   2470 			discard_repetition_stack();                                \
   2471 		}                                                                  \
   2472 	}                                                                          \
   2473 	clear_key_stack();                                                         \
   2474 	grab_char_cb = NULL;                                                       \
   2475 	return (struct action){ACTION_NONE, NULL};                                 \
   2476 }                                                                                  \
   2477                                                                                    \
   2478 static struct action                                                               \
   2479 name(ledit_view *view, char *text, size_t len) {                                   \
   2480 	(void)view;                                                                \
   2481 	(void)text;                                                                \
   2482 	(void)len;                                                                 \
   2483 	grab_char_cb = &name##_cb;                                                 \
   2484 	return (struct action){ACTION_NONE, NULL};                                 \
   2485 }
   2486 
   2487 /* FIXME: more sensible names */
   2488 /* FIXME: dummy_cursor_helper is kind of ugly */
   2489 GEN_MOVE_TO_CHAR(
   2490     find_next_char_forwards, search_str_forwards,
   2491     dummy_cursor_helper, view_prev_cursor_pos, dummy_cursor_helper
   2492 )
   2493 GEN_MOVE_TO_CHAR(
   2494     find_next_char_backwards, search_str_backwards,
   2495     view_next_cursor_pos, view_next_cursor_pos, view_next_cursor_pos
   2496 )
   2497 GEN_MOVE_TO_CHAR(
   2498     find_char_forwards, search_str_forwards,
   2499     view_next_cursor_pos, dummy_cursor_helper, dummy_cursor_helper
   2500 )
   2501 GEN_MOVE_TO_CHAR(
   2502     find_char_backwards, search_str_backwards,
   2503     dummy_cursor_helper, dummy_cursor_helper, dummy_cursor_helper
   2504 )
   2505 
   2506 static struct action
   2507 loweruppercase(ledit_view *view, int upper) {
   2508 	/* FIXME: shouldn't CHECK_VIEW_LOCKED be in a lot more places? */
   2509 	CHECK_VIEW_LOCKED(view);
   2510 	/* FIXME: move most of this to convenience functions in buffer.c */
   2511 	ledit_line *line = buffer_get_line(view->buffer, view->cur_line);
   2512 	if (view->cur_index >= line->len)
   2513 		return (struct action){ACTION_NONE, NULL};
   2514 	buffer_normalize_line(line);
   2515 	size_t start_index = view->cur_index;
   2516 #if ENABLE_UTF8PROC
   2517 	utf8proc_int32_t c;
   2518 	/* FIXME: this cast to utf8proc_uint8_t probably doesn't break anything, but could it? */
   2519 	utf8proc_ssize_t origlen = utf8proc_iterate((utf8proc_uint8_t *)line->text + start_index, line->len - start_index, &c);
   2520 	/* FIXME: show error message? */
   2521 	if (c < 0 || origlen < 1)
   2522 		return (struct action){ACTION_NONE, NULL};
   2523 	utf8proc_int32_t u;
   2524 	if (upper)
   2525 		u = utf8proc_toupper(c);
   2526 	else
   2527 		u = utf8proc_tolower(c);
   2528 	utf8proc_uint8_t u8[4];
   2529 	utf8proc_ssize_t newlen = utf8proc_encode_char(u, u8);
   2530 	if (newlen < 1)
   2531 		return (struct action){ACTION_NONE, NULL};
   2532 	delete_range(
   2533 	    view, 0, 0,
   2534 	    view->cur_line, start_index, view->cur_line, start_index + origlen, 0
   2535 	);
   2536 	insert_text(
   2537 	    view, view->cur_line, start_index, (char *)u8, newlen,
   2538 	    view->cur_line, start_index, view->cur_line, start_index, 1, 1, 0
   2539 	);
   2540 #else
   2541 	char c;
   2542 	if (upper)
   2543 		c = toupper((unsigned char)line->text[view->cur_index]);
   2544 	else
   2545 		c = tolower((unsigned char)line->text[view->cur_index]);
   2546 	delete_range(
   2547 	    view, 0, 0,
   2548 	    view->cur_line, start_index, view->cur_line, start_index + 1, 0
   2549 	);
   2550 	insert_text(
   2551 	    view, view->cur_line, start_index, &c, 1,
   2552 	    view->cur_line, start_index, view->cur_line, start_index, 1, 1, 0
   2553 	);
   2554 #endif
   2555 	/* If the last character on a line is replaced, the cursor would jump
   2556 	   backwards due to the deletion, so it has to be set to the original
   2557 	   position again */
   2558 	view->cur_index = start_index;
   2559 	push_undo_empty_insert(view, view->cur_line, view->cur_index, 0);
   2560 	if (view->mode == NORMAL)
   2561 		view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
   2562 	finalize_repetition_stack();
   2563 	return (struct action){ACTION_NONE, NULL};
   2564 }
   2565 
   2566 static struct action
   2567 uppercase(ledit_view *view, char *text, size_t len) {
   2568 	(void)text;
   2569 	(void)len;
   2570 	return loweruppercase(view, 1);
   2571 }
   2572 
   2573 static struct action
   2574 lowercase(ledit_view *view, char *text, size_t len) {
   2575 	(void)text;
   2576 	(void)len;
   2577 	return loweruppercase(view, 0);
   2578 }
   2579 
   2580 static struct action
   2581 replace_cb(ledit_view *view, char *text, size_t len) {
   2582 	CHECK_VIEW_LOCKED(view);
   2583 	size_t start_index = view->cur_index;
   2584 	/* FIXME: replace with (key repeat) * text instead of just text */
   2585 	/* FIXME: cursor pos or char? */
   2586 	size_t end_index;
   2587 	view_next_cursor_pos(
   2588 	    view, view->cur_line, view->cur_index, 1, 0, NULL, &end_index
   2589 	);
   2590 	delete_range(
   2591 	    view, 0, 0,
   2592 	    view->cur_line, start_index, view->cur_line, end_index, 0
   2593 	);
   2594 	insert_text(
   2595 	    view, view->cur_line, start_index, text, len,
   2596 	    view->cur_line, start_index, view->cur_line, start_index, 1, 1, 0
   2597 	);
   2598 	/* If the last character on a line is replaced, the cursor would jump
   2599 	   backwards due to the deletion, so it has to be set to the original
   2600 	   position again */
   2601 	view->cur_index = start_index;
   2602 	push_undo_empty_insert(view, view->cur_line, view->cur_index, 0);
   2603 	if (view->mode == NORMAL)
   2604 		view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
   2605 	grab_char_cb = NULL;
   2606 	finalize_repetition_stack();
   2607 	return (struct action){ACTION_NONE, NULL};
   2608 }
   2609 
   2610 static struct action
   2611 replace(ledit_view *view, char *text, size_t len) {
   2612 	(void)view;
   2613 	(void)text;
   2614 	(void)len;
   2615 	int num = get_key_repeat();
   2616 	if (num != 0)
   2617 		return err_invalid_key(view);
   2618 	grab_char_cb = &replace_cb;
   2619 	return (struct action){ACTION_NONE, NULL};
   2620 }
   2621 
   2622 static struct action
   2623 toggle_hard_line_based(ledit_view *view, char *text, size_t len) {
   2624 	(void)view;
   2625 	(void)text;
   2626 	(void)len;
   2627 	int num = get_key_repeat();
   2628 	if (num != 0)
   2629 		return err_invalid_key(view);
   2630 	buffer_set_hard_line_based(view->buffer, !view->buffer->hard_line_based);
   2631 	discard_repetition_stack();
   2632 	return (struct action){ACTION_NONE, NULL};
   2633 }
   2634 
   2635 static struct action
   2636 handle_key(ledit_view *view, char *key_text, size_t len, KeySym sym, unsigned int key_state, size_t lang_index, int *found, basic_key_cb_flags *flags) {
   2637 	basic_key_array *cur_keys = config_get_basic_keys(lang_index);
   2638 	size_t num_keys = cur_keys->num_keys;
   2639 	/* FIXME: check if control chars in text */
   2640 	/* FIXME: this is a bit of a hack because it's hardcoded */
   2641 	if (grab_char_cb && sym == XK_Escape) {
   2642 		grab_char_cb = NULL;
   2643 		return (struct action){ACTION_NONE, NULL};
   2644 	} else if (len > 0 && grab_char_cb) {
   2645 		*found = 1;
   2646 		*flags = 0;
   2647 		return grab_char_cb(view, key_text, len);
   2648 	}
   2649 	*found = 0;
   2650 	for (size_t i = 0; i < num_keys; i++) {
   2651 		if (cur_keys->keys[i].text) {
   2652 			if (len > 0 &&
   2653 			    (cur_keys->keys[i].modes & view->mode) &&
   2654 			     ((!strncmp(cur_keys->keys[i].text, key_text, len) &&
   2655 			       match_key(cur_keys->keys[i].mods, key_state & ~ShiftMask)) ||
   2656 			      cur_keys->keys[i].text[0] == '\0')) {
   2657 				/* FIXME: seems a bit hacky to remove shift, but it
   2658 				   is needed to make keys that use shift match */
   2659 				*flags = cur_keys->keys[i].cb->flags;
   2660 				*found = 1;
   2661 				if (!(*flags & KEY_FLAG_LOCK_ALLOWED) && view->lock_text)
   2662 					return view_locked_error(view);
   2663 				return cur_keys->keys[i].cb->func(view, key_text, len);
   2664 			}
   2665 		} else if ((cur_keys->keys[i].modes & view->mode) &&
   2666 		           cur_keys->keys[i].keysym == sym &&
   2667 			   match_key(cur_keys->keys[i].mods, key_state)) {
   2668 			*flags = cur_keys->keys[i].cb->flags;
   2669 			*found = 1;
   2670 			if (!(*flags & KEY_FLAG_LOCK_ALLOWED) && view->lock_text)
   2671 				return view_locked_error(view);
   2672 			return cur_keys->keys[i].cb->func(view, key_text, len);
   2673 		}
   2674 	}
   2675 	return (struct action){ACTION_NONE, NULL};
   2676 }
   2677 
   2678 static struct action
   2679 repeat_command(ledit_view *view, char *text, size_t len) {
   2680 	(void)view;
   2681 	(void)text;
   2682 	(void)len;
   2683 	int num = get_key_repeat();
   2684 	if (num == -1)
   2685 		return err_invalid_key(view);
   2686 	if (num == 0)
   2687 		num = 1;
   2688 	if (repetition_stack.len == 0) {
   2689 		window_show_message(view->window, "No previous command", -1);
   2690 		discard_repetition_stack();
   2691 		return (struct action){ACTION_NONE, NULL};
   2692 	}
   2693 	int found;
   2694 	basic_key_cb_flags flags;
   2695 	repetition_stack.replaying = 1;
   2696 	for (int i = 0; i < num; i++) {
   2697 		unwind_repetition_stack();
   2698 		struct repetition_stack_elem *e = get_cur_repetition_stack_elem();
   2699 		while (e) {
   2700 			(void)handle_key(view, e->key_text, e->len, e->sym, e->key_state, e->lang_index, &found, &flags);
   2701 			advance_repetition_stack();
   2702 			e = get_cur_repetition_stack_elem();
   2703 		}
   2704 	}
   2705 	repetition_stack.replaying = 0;
   2706 	discard_repetition_stack();
   2707 	clear_key_stack();
   2708 	return (struct action){ACTION_NONE, NULL};
   2709 }
   2710 
   2711 struct action
   2712 basic_key_handler(ledit_view *view, unsigned int key_state, KeySym sym, char *buf, int n, int lang_index) {
   2713 	struct repetition_stack_elem *re = push_repetition_stack();
   2714 	re->key_text = ledit_strndup(buf, (size_t)n);
   2715 	re->len = (size_t)n;
   2716 	re->sym = sym;
   2717 	re->key_state = key_state;
   2718 	re->lang_index = lang_index;
   2719 
   2720 	/* FIXME: figure out when to actually hide message and
   2721 	   ensure cursor shown */
   2722 	/* FIXME: clean up interface (what has a setter method and what not) */
   2723 	int found = 0;
   2724 	int msg_shown = view->window->message_shown;
   2725 	view->window->message_shown = 0; /* FIXME: this is hacky */
   2726 	basic_key_cb_flags flags;
   2727 	struct action act = handle_key(view, buf, (size_t)n, sym, key_state, lang_index, &found, &flags);
   2728 	/* FIXME: make message hiding a property of each command so e.g. cursor keys also hide it */
   2729 	if (found && n > 0 && !view->window->message_shown)
   2730 		window_hide_message(view->window);
   2731 	else if (msg_shown)
   2732 		view->window->message_shown = msg_shown;
   2733 
   2734 	if (found && (flags & KEY_FLAG_JUMP_TO_CURSOR))
   2735 		view_ensure_cursor_shown(view);
   2736 	/* FIXME: this also doesn't show real invalid keys in insert mode
   2737 	   -> it needs to be this way to avoid showing anything on modifier
   2738 	   keys, but maybe it should just check explicitly for modifier keys? */
   2739 	if (!found && n > 0) {
   2740 		window_show_message(view->window, "Invalid key", -1);
   2741 		discard_repetition_stack();
   2742 		clear_key_stack();
   2743 	}
   2744 	return act;
   2745 }