URI: 
       keys_basic.c - ledit - Text editor (WIP)
  HTML git clone git://lumidify.org/ledit.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/ledit.git (encrypted, but very slow)
  HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ledit.git (over tor)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       keys_basic.c (104268B)
       ---
            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); /* FIXME: why do I not return here? */
         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 }