vis

a vi-like editor based on Plan 9's structural regular expressions

git clone https://9o.is/git/vis.git

main.c

(71802B)


      1 #include <signal.h>
      2 #include <limits.h>
      3 #include <string.h>
      4 #include <stdio.h>
      5 #include <wchar.h>
      6 #include <ctype.h>
      7 #include <errno.h>
      8 #include <fcntl.h>
      9 #include <inttypes.h>
     10 #include <sys/stat.h>
     11 #include <sys/types.h>
     12 
     13 #include "ui.h"
     14 #include "vis.h"
     15 #include "vis-lua.h"
     16 #include "text-util.h"
     17 #include "text-motions.h"
     18 #include "text-objects.h"
     19 #include "util.h"
     20 #include "libutf.h"
     21 #include "array.h"
     22 #include "buffer.h"
     23 
     24 #define PAGE      INT_MAX
     25 #define PAGE_HALF (INT_MAX-1)
     26 
     27 /** functions to be called from keybindings */
     28 /* ignore key, do nothing */
     29 static const char *nop(Vis*, const char *keys, const Arg *arg);
     30 /* record/replay macro indicated by keys */
     31 static const char *macro_record(Vis*, const char *keys, const Arg *arg);
     32 static const char *macro_replay(Vis*, const char *keys, const Arg *arg);
     33 /* temporarily suspend the editor and return to the shell, type 'fg' to get back */
     34 static const char *suspend(Vis*, const char *keys, const Arg *arg);
     35 /* reset count if set, otherwise remove all but the primary selection */
     36 static const char *normalmode_escape(Vis*, const char *keys, const Arg *arg);
     37 /* reset count if set, otherwise switch to normal mode */
     38 static const char *visualmode_escape(Vis*, const char *keys, const Arg *arg);
     39 /* switch to mode indicated by arg->i */
     40 static const char *switchmode(Vis*, const char *keys, const Arg *arg);
     41 /* switch to insert mode after performing movement indicated by arg->i */
     42 static const char *insertmode(Vis*, const char *keys, const Arg *arg);
     43 /* switch to replace mode after performing movement indicated by arg->i */
     44 static const char *replacemode(Vis*, const char *keys, const Arg *arg);
     45 /* add a new line either before or after the one where the cursor currently is */
     46 static const char *openline(Vis*, const char *keys, const Arg *arg);
     47 /* join lines from current cursor position to movement indicated by arg */
     48 static const char *join(Vis*, const char *keys, const Arg *arg);
     49 /* perform last action i.e. action_prev again */
     50 static const char *repeat(Vis*, const char *keys, const Arg *arg);
     51 /* replace character at cursor with one from keys */
     52 static const char *replace(Vis*, const char *keys, const Arg *arg);
     53 /* create a new cursor on the previous (arg->i < 0) or next (arg->i > 0) line */
     54 static const char *selections_new(Vis*, const char *keys, const Arg *arg);
     55 /* try to align all selections on the same column */
     56 static const char *selections_align(Vis*, const char *keys, const Arg *arg);
     57 /* try to align all selections by inserting the correct amount of white spaces */
     58 static const char *selections_align_indent(Vis*, const char *keys, const Arg *arg);
     59 /* remove all but the primary cursor and their selections */
     60 static const char *selections_clear(Vis*, const char *keys, const Arg *arg);
     61 /* remove the least recently added selection */
     62 static const char *selections_remove(Vis*, const char *keys, const Arg *arg);
     63 /* remove count (or arg->i)-th selection column */
     64 static const char *selections_remove_column(Vis*, const char *keys, const Arg *arg);
     65 /* remove all but the count (or arg->i)-th selection column */
     66 static const char *selections_remove_column_except(Vis*, const char *keys, const Arg *arg);
     67 /* move to the previous (arg->i < 0) or next (arg->i > 0) selection */
     68 static const char *selections_navigate(Vis*, const char *keys, const Arg *arg);
     69 /* select the next region matching the current selection */
     70 static const char *selections_match_next(Vis*, const char *keys, const Arg *arg);
     71 /* clear current selection but select next match */
     72 static const char *selections_match_skip(Vis*, const char *keys, const Arg *arg);
     73 /* rotate selection content count times left (arg->i < 0) or right (arg->i > 0) */
     74 static const char *selections_rotate(Vis*, const char *keys, const Arg *arg);
     75 /* remove leading and trailing white spaces from selections */
     76 static const char *selections_trim(Vis*, const char *keys, const Arg *arg);
     77 /* save active selections to mark */
     78 static const char *selections_save(Vis*, const char *keys, const Arg *arg);
     79 /* restore selections from mark */
     80 static const char *selections_restore(Vis*, const char *keys, const Arg *arg);
     81 /* union selections from mark */
     82 static const char *selections_union(Vis*, const char *keys, const Arg *arg);
     83 /* intersect selections from mark */
     84 static const char *selections_intersect(Vis*, const char *keys, const Arg *arg);
     85 /* perform complement of current active selections */
     86 static const char *selections_complement(Vis*, const char *keys, const Arg *arg);
     87 /* subtract selections from mark */
     88 static const char *selections_minus(Vis*, const char *keys, const Arg *arg);
     89 /* adjust current used count according to keys */
     90 static const char *count(Vis*, const char *keys, const Arg *arg);
     91 /* move to the count-th line or if not given either to the first (arg->i < 0)
     92  *  or last (arg->i > 0) line of file */
     93 static const char *gotoline(Vis*, const char *keys, const Arg *arg);
     94 /* make the current action use the operator indicated by arg->i */
     95 static const char *operator(Vis*, const char *keys, const Arg *arg);
     96 /* blocks to read a key and performs movement indicated by arg->i which
     97  * should be one of VIS_MOVE_{TO,TILL}_{,LINE}_{RIGHT,LEFT}*/
     98 static const char *movement_key(Vis*, const char *keys, const Arg *arg);
     99 /* perform the movement as indicated by arg->i */
    100 static const char *movement(Vis*, const char *keys, const Arg *arg);
    101 /* let the current operator affect the range indicated by the text object arg->i */
    102 static const char *textobj(Vis*, const char *keys, const Arg *arg);
    103 /* move to the other end of selected text */
    104 static const char *selection_end(Vis*, const char *keys, const Arg *arg);
    105 /* use register indicated by keys for the current operator */
    106 static const char *reg(Vis*, const char *keys, const Arg *arg);
    107 /* use mark indicated by keys for the current action */
    108 static const char *mark(Vis*, const char *keys, const Arg *arg);
    109 /* {un,re}do last action, redraw window */
    110 static const char *undo(Vis*, const char *keys, const Arg *arg);
    111 static const char *redo(Vis*, const char *keys, const Arg *arg);
    112 /* earlier, later action chronologically, redraw window */
    113 static const char *earlier(Vis*, const char *keys, const Arg *arg);
    114 static const char *later(Vis*, const char *keys, const Arg *arg);
    115 /* delete from the current cursor position to the end of
    116  * movement as indicated by arg->i */
    117 static const char *delete(Vis*, const char *keys, const Arg *arg);
    118 /* insert register content indicated by keys at current cursor position */
    119 static const char *insert_register(Vis*, const char *keys, const Arg *arg);
    120 /* show a user prompt to get input with title arg->s */
    121 static const char *prompt_show(Vis*, const char *keys, const Arg *arg);
    122 /* blocks to read 3 consecutive digits and inserts the corresponding byte value */
    123 static const char *insert_verbatim(Vis*, const char *keys, const Arg *arg);
    124 /* scroll window content according to arg->i which can be either PAGE, PAGE_HALF,
    125  * or an arbitrary number of lines. a multiplier overrides what is given in arg->i.
    126  * negative values scroll back, positive forward. */
    127 static const char *wscroll(Vis*, const char *keys, const Arg *arg);
    128 /* similar to scroll, but do only move window content not cursor position */
    129 static const char *wslide(Vis*, const char *keys, const Arg *arg);
    130 /* call editor function as indicated by arg->f */
    131 static const char *call(Vis*, const char *keys, const Arg *arg);
    132 /* call window function as indicated by arg->w */
    133 static const char *window(Vis*, const char *keys, const Arg *arg);
    134 /* show info about Unicode character at cursor position */
    135 static const char *unicode_info(Vis*, const char *keys, const Arg *arg);
    136 /* either go to count % of file or to matching item */
    137 static const char *percent(Vis*, const char *keys, const Arg *arg);
    138 /* navigate jumplist next (arg->i > 0), prev (arg->i < 0), save (arg->i = 0) */
    139 static const char *jumplist(Vis*, const char *keys, const Arg *arg);
    140 
    141 enum {
    142 	VIS_ACTION_EDITOR_SUSPEND,
    143 	VIS_ACTION_CURSOR_CHAR_PREV,
    144 	VIS_ACTION_CURSOR_CHAR_NEXT,
    145 	VIS_ACTION_CURSOR_LINE_CHAR_PREV,
    146 	VIS_ACTION_CURSOR_LINE_CHAR_NEXT,
    147 	VIS_ACTION_CURSOR_CODEPOINT_PREV,
    148 	VIS_ACTION_CURSOR_CODEPOINT_NEXT,
    149 	VIS_ACTION_CURSOR_WORD_START_PREV,
    150 	VIS_ACTION_CURSOR_WORD_START_NEXT,
    151 	VIS_ACTION_CURSOR_WORD_END_PREV,
    152 	VIS_ACTION_CURSOR_WORD_END_NEXT,
    153 	VIS_ACTION_CURSOR_LONGWORD_START_PREV,
    154 	VIS_ACTION_CURSOR_LONGWORD_START_NEXT,
    155 	VIS_ACTION_CURSOR_LONGWORD_END_PREV,
    156 	VIS_ACTION_CURSOR_LONGWORD_END_NEXT,
    157 	VIS_ACTION_CURSOR_LINE_UP,
    158 	VIS_ACTION_CURSOR_LINE_DOWN,
    159 	VIS_ACTION_CURSOR_LINE_START,
    160 	VIS_ACTION_CURSOR_LINE_FINISH,
    161 	VIS_ACTION_CURSOR_LINE_BEGIN,
    162 	VIS_ACTION_CURSOR_LINE_END,
    163 	VIS_ACTION_CURSOR_SCREEN_LINE_UP,
    164 	VIS_ACTION_CURSOR_SCREEN_LINE_DOWN,
    165 	VIS_ACTION_CURSOR_SCREEN_LINE_BEGIN,
    166 	VIS_ACTION_CURSOR_SCREEN_LINE_MIDDLE,
    167 	VIS_ACTION_CURSOR_SCREEN_LINE_END,
    168 	VIS_ACTION_CURSOR_PERCENT,
    169 	VIS_ACTION_CURSOR_BYTE,
    170 	VIS_ACTION_CURSOR_BYTE_LEFT,
    171 	VIS_ACTION_CURSOR_BYTE_RIGHT,
    172 	VIS_ACTION_CURSOR_PARAGRAPH_PREV,
    173 	VIS_ACTION_CURSOR_PARAGRAPH_NEXT,
    174 	VIS_ACTION_CURSOR_SENTENCE_PREV,
    175 	VIS_ACTION_CURSOR_SENTENCE_NEXT,
    176 	VIS_ACTION_CURSOR_BLOCK_START,
    177 	VIS_ACTION_CURSOR_BLOCK_END,
    178 	VIS_ACTION_CURSOR_PARENTHESIS_START,
    179 	VIS_ACTION_CURSOR_PARENTHESIS_END,
    180 	VIS_ACTION_CURSOR_COLUMN,
    181 	VIS_ACTION_CURSOR_LINE_FIRST,
    182 	VIS_ACTION_CURSOR_LINE_LAST,
    183 	VIS_ACTION_CURSOR_WINDOW_LINE_TOP,
    184 	VIS_ACTION_CURSOR_WINDOW_LINE_MIDDLE,
    185 	VIS_ACTION_CURSOR_WINDOW_LINE_BOTTOM,
    186 	VIS_ACTION_CURSOR_SEARCH_REPEAT_FORWARD,
    187 	VIS_ACTION_CURSOR_SEARCH_REPEAT_BACKWARD,
    188 	VIS_ACTION_CURSOR_SEARCH_REPEAT,
    189 	VIS_ACTION_CURSOR_SEARCH_REPEAT_REVERSE,
    190 	VIS_ACTION_CURSOR_SEARCH_WORD_FORWARD,
    191 	VIS_ACTION_CURSOR_SEARCH_WORD_BACKWARD,
    192 	VIS_ACTION_WINDOW_PAGE_UP,
    193 	VIS_ACTION_WINDOW_PAGE_DOWN,
    194 	VIS_ACTION_WINDOW_HALFPAGE_UP,
    195 	VIS_ACTION_WINDOW_HALFPAGE_DOWN,
    196 	VIS_ACTION_MODE_NORMAL,
    197 	VIS_ACTION_MODE_NORMAL_ESCAPE,
    198 	VIS_ACTION_MODE_VISUAL,
    199 	VIS_ACTION_MODE_VISUAL_ESCAPE,
    200 	VIS_ACTION_MODE_VISUAL_LINE,
    201 	VIS_ACTION_MODE_INSERT,
    202 	VIS_ACTION_MODE_REPLACE,
    203 	VIS_ACTION_DELETE_CHAR_PREV,
    204 	VIS_ACTION_DELETE_CHAR_NEXT,
    205 	VIS_ACTION_DELETE_LINE_BEGIN,
    206 	VIS_ACTION_DELETE_WORD_PREV,
    207 	VIS_ACTION_JUMPLIST_PREV,
    208 	VIS_ACTION_JUMPLIST_NEXT,
    209 	VIS_ACTION_JUMPLIST_SAVE,
    210 	VIS_ACTION_UNDO,
    211 	VIS_ACTION_REDO,
    212 	VIS_ACTION_EARLIER,
    213 	VIS_ACTION_LATER,
    214 	VIS_ACTION_MACRO_RECORD,
    215 	VIS_ACTION_MACRO_REPLAY,
    216 	VIS_ACTION_MARK,
    217 	VIS_ACTION_REDRAW,
    218 	VIS_ACTION_REPLACE_CHAR,
    219 	VIS_ACTION_TOTILL_REPEAT,
    220 	VIS_ACTION_TOTILL_REVERSE,
    221 	VIS_ACTION_PROMPT_SEARCH_FORWARD,
    222 	VIS_ACTION_PROMPT_SEARCH_BACKWARD,
    223 	VIS_ACTION_TILL_LEFT,
    224 	VIS_ACTION_TILL_RIGHT,
    225 	VIS_ACTION_TILL_LINE_LEFT,
    226 	VIS_ACTION_TILL_LINE_RIGHT,
    227 	VIS_ACTION_TO_LEFT,
    228 	VIS_ACTION_TO_RIGHT,
    229 	VIS_ACTION_TO_LINE_LEFT,
    230 	VIS_ACTION_TO_LINE_RIGHT,
    231 	VIS_ACTION_REGISTER,
    232 	VIS_ACTION_OPERATOR_CHANGE,
    233 	VIS_ACTION_OPERATOR_DELETE,
    234 	VIS_ACTION_OPERATOR_YANK,
    235 	VIS_ACTION_OPERATOR_SHIFT_LEFT,
    236 	VIS_ACTION_OPERATOR_SHIFT_RIGHT,
    237 	VIS_ACTION_COUNT,
    238 	VIS_ACTION_INSERT_NEWLINE,
    239 	VIS_ACTION_INSERT_TAB,
    240 	VIS_ACTION_INSERT_VERBATIM,
    241 	VIS_ACTION_INSERT_REGISTER,
    242 	VIS_ACTION_WINDOW_NEXT,
    243 	VIS_ACTION_WINDOW_PREV,
    244 	VIS_ACTION_APPEND_CHAR_NEXT,
    245 	VIS_ACTION_APPEND_LINE_END,
    246 	VIS_ACTION_INSERT_LINE_START,
    247 	VIS_ACTION_OPEN_LINE_ABOVE,
    248 	VIS_ACTION_OPEN_LINE_BELOW,
    249 	VIS_ACTION_JOIN_LINES,
    250 	VIS_ACTION_JOIN_LINES_TRIM,
    251 	VIS_ACTION_PROMPT_SHOW,
    252 	VIS_ACTION_REPEAT,
    253 	VIS_ACTION_SELECTION_FLIP,
    254 	VIS_ACTION_WINDOW_REDRAW_TOP,
    255 	VIS_ACTION_WINDOW_REDRAW_CENTER,
    256 	VIS_ACTION_WINDOW_REDRAW_BOTTOM,
    257 	VIS_ACTION_WINDOW_SLIDE_UP,
    258 	VIS_ACTION_WINDOW_SLIDE_DOWN,
    259 	VIS_ACTION_PUT_AFTER,
    260 	VIS_ACTION_PUT_BEFORE,
    261 	VIS_ACTION_SELECTIONS_NEW_LINE_ABOVE,
    262 	VIS_ACTION_SELECTIONS_NEW_LINE_ABOVE_FIRST,
    263 	VIS_ACTION_SELECTIONS_NEW_LINE_BELOW,
    264 	VIS_ACTION_SELECTIONS_NEW_LINE_BELOW_LAST,
    265 	VIS_ACTION_SELECTIONS_NEW_LINES_BEGIN,
    266 	VIS_ACTION_SELECTIONS_NEW_LINES_END,
    267 	VIS_ACTION_SELECTIONS_NEW_MATCH_ALL,
    268 	VIS_ACTION_SELECTIONS_NEW_MATCH_NEXT,
    269 	VIS_ACTION_SELECTIONS_NEW_MATCH_SKIP,
    270 	VIS_ACTION_SELECTIONS_ALIGN,
    271 	VIS_ACTION_SELECTIONS_ALIGN_INDENT_LEFT,
    272 	VIS_ACTION_SELECTIONS_ALIGN_INDENT_RIGHT,
    273 	VIS_ACTION_SELECTIONS_REMOVE_ALL,
    274 	VIS_ACTION_SELECTIONS_REMOVE_LAST,
    275 	VIS_ACTION_SELECTIONS_REMOVE_COLUMN,
    276 	VIS_ACTION_SELECTIONS_REMOVE_COLUMN_EXCEPT,
    277 	VIS_ACTION_SELECTIONS_PREV,
    278 	VIS_ACTION_SELECTIONS_NEXT,
    279 	VIS_ACTION_SELECTIONS_ROTATE_LEFT,
    280 	VIS_ACTION_SELECTIONS_ROTATE_RIGHT,
    281 	VIS_ACTION_SELECTIONS_TRIM,
    282 	VIS_ACTION_SELECTIONS_SAVE,
    283 	VIS_ACTION_SELECTIONS_RESTORE,
    284 	VIS_ACTION_SELECTIONS_UNION,
    285 	VIS_ACTION_SELECTIONS_INTERSECT,
    286 	VIS_ACTION_SELECTIONS_COMPLEMENT,
    287 	VIS_ACTION_SELECTIONS_MINUS,
    288 	VIS_ACTION_TEXT_OBJECT_WORD_OUTER,
    289 	VIS_ACTION_TEXT_OBJECT_WORD_INNER,
    290 	VIS_ACTION_TEXT_OBJECT_LONGWORD_OUTER,
    291 	VIS_ACTION_TEXT_OBJECT_LONGWORD_INNER,
    292 	VIS_ACTION_TEXT_OBJECT_SENTENCE,
    293 	VIS_ACTION_TEXT_OBJECT_PARAGRAPH,
    294 	VIS_ACTION_TEXT_OBJECT_PARAGRAPH_OUTER,
    295 	VIS_ACTION_TEXT_OBJECT_SQUARE_BRACKET_OUTER,
    296 	VIS_ACTION_TEXT_OBJECT_SQUARE_BRACKET_INNER,
    297 	VIS_ACTION_TEXT_OBJECT_PARENTHESIS_OUTER,
    298 	VIS_ACTION_TEXT_OBJECT_PARENTHESIS_INNER,
    299 	VIS_ACTION_TEXT_OBJECT_ANGLE_BRACKET_OUTER,
    300 	VIS_ACTION_TEXT_OBJECT_ANGLE_BRACKET_INNER,
    301 	VIS_ACTION_TEXT_OBJECT_CURLY_BRACKET_OUTER,
    302 	VIS_ACTION_TEXT_OBJECT_CURLY_BRACKET_INNER,
    303 	VIS_ACTION_TEXT_OBJECT_QUOTE_OUTER,
    304 	VIS_ACTION_TEXT_OBJECT_QUOTE_INNER,
    305 	VIS_ACTION_TEXT_OBJECT_SINGLE_QUOTE_OUTER,
    306 	VIS_ACTION_TEXT_OBJECT_SINGLE_QUOTE_INNER,
    307 	VIS_ACTION_TEXT_OBJECT_BACKTICK_OUTER,
    308 	VIS_ACTION_TEXT_OBJECT_BACKTICK_INNER,
    309 	VIS_ACTION_TEXT_OBJECT_LINE_OUTER,
    310 	VIS_ACTION_TEXT_OBJECT_LINE_INNER,
    311 	VIS_ACTION_TEXT_OBJECT_INDENTATION,
    312 	VIS_ACTION_TEXT_OBJECT_SEARCH_FORWARD,
    313 	VIS_ACTION_TEXT_OBJECT_SEARCH_BACKWARD,
    314 	VIS_ACTION_UNICODE_INFO,
    315 	VIS_ACTION_UTF8_INFO,
    316 	VIS_ACTION_NOP,
    317 };
    318 
    319 static const KeyAction vis_action[] = {
    320 	[VIS_ACTION_EDITOR_SUSPEND] = {
    321 		"vis-suspend",
    322 		VIS_HELP("Suspend the editor")
    323 		suspend,
    324 	},
    325 	[VIS_ACTION_CURSOR_CHAR_PREV] = {
    326 		"vis-motion-char-prev",
    327 		VIS_HELP("Move cursor left, to the previous character")
    328 		movement, { .i = VIS_MOVE_CHAR_PREV }
    329 	},
    330 	[VIS_ACTION_CURSOR_CHAR_NEXT] = {
    331 		"vis-motion-char-next",
    332 		VIS_HELP("Move cursor right, to the next character")
    333 		movement, { .i = VIS_MOVE_CHAR_NEXT }
    334 	},
    335 	[VIS_ACTION_CURSOR_LINE_CHAR_PREV] = {
    336 		"vis-motion-line-char-prev",
    337 		VIS_HELP("Move cursor left, to the previous character on the same line")
    338 		movement, { .i = VIS_MOVE_LINE_CHAR_PREV }
    339 	},
    340 	[VIS_ACTION_CURSOR_LINE_CHAR_NEXT] = {
    341 		"vis-motion-line-char-next",
    342 		VIS_HELP("Move cursor right, to the next character on the same line")
    343 		movement, { .i = VIS_MOVE_LINE_CHAR_NEXT }
    344 	},
    345 	[VIS_ACTION_CURSOR_CODEPOINT_PREV] = {
    346 		"vis-motion-codepoint-prev",
    347 		VIS_HELP("Move to the previous Unicode codepoint")
    348 		movement, { .i = VIS_MOVE_CODEPOINT_PREV }
    349 	},
    350 	[VIS_ACTION_CURSOR_CODEPOINT_NEXT] = {
    351 		"vis-motion-codepoint-next",
    352 		VIS_HELP("Move to the next Unicode codepoint")
    353 		movement, { .i = VIS_MOVE_CODEPOINT_NEXT }
    354 	},
    355 	[VIS_ACTION_CURSOR_WORD_START_PREV] = {
    356 		"vis-motion-word-start-prev",
    357 		VIS_HELP("Move cursor words backwards")
    358 		movement, { .i = VIS_MOVE_WORD_START_PREV }
    359 	},
    360 	[VIS_ACTION_CURSOR_WORD_START_NEXT] = {
    361 		"vis-motion-word-start-next",
    362 		VIS_HELP("Move cursor words forwards")
    363 		movement, { .i = VIS_MOVE_WORD_START_NEXT }
    364 	},
    365 	[VIS_ACTION_CURSOR_WORD_END_PREV] = {
    366 		"vis-motion-word-end-prev",
    367 		VIS_HELP("Move cursor backwards to the end of word")
    368 		movement, { .i = VIS_MOVE_WORD_END_PREV }
    369 	},
    370 	[VIS_ACTION_CURSOR_WORD_END_NEXT] = {
    371 		"vis-motion-word-end-next",
    372 		VIS_HELP("Move cursor forward to the end of word")
    373 		movement, { .i = VIS_MOVE_WORD_END_NEXT }
    374 	},
    375 	[VIS_ACTION_CURSOR_LONGWORD_START_PREV] = {
    376 		"vis-motion-bigword-start-prev",
    377 		VIS_HELP("Move cursor WORDS backwards")
    378 		movement, { .i = VIS_MOVE_LONGWORD_START_PREV }
    379 	},
    380 	[VIS_ACTION_CURSOR_LONGWORD_START_NEXT] = {
    381 		"vis-motion-bigword-start-next",
    382 		VIS_HELP("Move cursor WORDS forwards")
    383 		movement, { .i = VIS_MOVE_LONGWORD_START_NEXT }
    384 	},
    385 	[VIS_ACTION_CURSOR_LONGWORD_END_PREV] = {
    386 		"vis-motion-bigword-end-prev",
    387 		VIS_HELP("Move cursor backwards to the end of WORD")
    388 		movement, { .i = VIS_MOVE_LONGWORD_END_PREV }
    389 	},
    390 	[VIS_ACTION_CURSOR_LONGWORD_END_NEXT] = {
    391 		"vis-motion-bigword-end-next",
    392 		VIS_HELP("Move cursor forward to the end of WORD")
    393 		movement, { .i = VIS_MOVE_LONGWORD_END_NEXT }
    394 	},
    395 	[VIS_ACTION_CURSOR_LINE_UP] = {
    396 		"vis-motion-line-up",
    397 		VIS_HELP("Move cursor line upwards")
    398 		movement, { .i = VIS_MOVE_LINE_UP }
    399 	},
    400 	[VIS_ACTION_CURSOR_LINE_DOWN] = {
    401 		"vis-motion-line-down",
    402 		VIS_HELP("Move cursor line downwards")
    403 		movement, { .i = VIS_MOVE_LINE_DOWN }
    404 	},
    405 	[VIS_ACTION_CURSOR_LINE_START] = {
    406 		"vis-motion-line-start",
    407 		VIS_HELP("Move cursor to first non-blank character of the line")
    408 		movement, { .i = VIS_MOVE_LINE_START }
    409 	},
    410 	[VIS_ACTION_CURSOR_LINE_FINISH] = {
    411 		"vis-motion-line-finish",
    412 		VIS_HELP("Move cursor to last non-blank character of the line")
    413 		movement, { .i = VIS_MOVE_LINE_FINISH }
    414 	},
    415 	[VIS_ACTION_CURSOR_LINE_BEGIN] = {
    416 		"vis-motion-line-begin",
    417 		VIS_HELP("Move cursor to first character of the line")
    418 		movement, { .i = VIS_MOVE_LINE_BEGIN }
    419 	},
    420 	[VIS_ACTION_CURSOR_LINE_END] = {
    421 		"vis-motion-line-end",
    422 		VIS_HELP("Move cursor to end of the line")
    423 		movement, { .i = VIS_MOVE_LINE_END }
    424 	},
    425 	[VIS_ACTION_CURSOR_SCREEN_LINE_UP] = {
    426 		"vis-motion-screenline-up",
    427 		VIS_HELP("Move cursor screen/display line upwards")
    428 		movement, { .i = VIS_MOVE_SCREEN_LINE_UP }
    429 	},
    430 	[VIS_ACTION_CURSOR_SCREEN_LINE_DOWN] = {
    431 		"vis-motion-screenline-down",
    432 		VIS_HELP("Move cursor screen/display line downwards")
    433 		movement, { .i = VIS_MOVE_SCREEN_LINE_DOWN }
    434 	},
    435 	[VIS_ACTION_CURSOR_SCREEN_LINE_BEGIN] = {
    436 		"vis-motion-screenline-begin",
    437 		VIS_HELP("Move cursor to beginning of screen/display line")
    438 		movement, { .i = VIS_MOVE_SCREEN_LINE_BEGIN }
    439 	},
    440 	[VIS_ACTION_CURSOR_SCREEN_LINE_MIDDLE] = {
    441 		"vis-motion-screenline-middle",
    442 		VIS_HELP("Move cursor to middle of screen/display line")
    443 		movement, { .i = VIS_MOVE_SCREEN_LINE_MIDDLE }
    444 	},
    445 	[VIS_ACTION_CURSOR_SCREEN_LINE_END] = {
    446 		"vis-motion-screenline-end",
    447 		VIS_HELP("Move cursor to end of screen/display line")
    448 		movement, { .i = VIS_MOVE_SCREEN_LINE_END }
    449 	},
    450 	[VIS_ACTION_CURSOR_PERCENT] = {
    451 		"vis-motion-percent",
    452 		VIS_HELP("Move to count % of file or matching item")
    453 		percent
    454 	},
    455 	[VIS_ACTION_CURSOR_BYTE] = {
    456 		"vis-motion-byte",
    457 		VIS_HELP("Move to absolute byte position")
    458 		movement, { .i = VIS_MOVE_BYTE }
    459 	},
    460 	[VIS_ACTION_CURSOR_BYTE_LEFT] = {
    461 		"vis-motion-byte-left",
    462 		VIS_HELP("Move count bytes to the left")
    463 		movement, { .i = VIS_MOVE_BYTE_LEFT }
    464 	},
    465 	[VIS_ACTION_CURSOR_BYTE_RIGHT] = {
    466 		"vis-motion-byte-right",
    467 		VIS_HELP("Move count bytes to the right")
    468 		movement, { .i = VIS_MOVE_BYTE_RIGHT }
    469 	},
    470 	[VIS_ACTION_CURSOR_PARAGRAPH_PREV] = {
    471 		"vis-motion-paragraph-prev",
    472 		VIS_HELP("Move cursor paragraph backward")
    473 		movement, { .i = VIS_MOVE_PARAGRAPH_PREV }
    474 	},
    475 	[VIS_ACTION_CURSOR_PARAGRAPH_NEXT] = {
    476 		"vis-motion-paragraph-next",
    477 		VIS_HELP("Move cursor paragraph forward")
    478 		movement, { .i = VIS_MOVE_PARAGRAPH_NEXT }
    479 	},
    480 	[VIS_ACTION_CURSOR_SENTENCE_PREV] = {
    481 		"vis-motion-sentence-prev",
    482 		VIS_HELP("Move cursor sentence backward")
    483 		movement, { .i = VIS_MOVE_SENTENCE_PREV }
    484 	},
    485 	[VIS_ACTION_CURSOR_SENTENCE_NEXT] = {
    486 		"vis-motion-sentence-next",
    487 		VIS_HELP("Move cursor sentence forward")
    488 		movement, { .i = VIS_MOVE_SENTENCE_NEXT }
    489 	},
    490 	[VIS_ACTION_CURSOR_BLOCK_START] = {
    491 		"vis-motion-block-start",
    492 		VIS_HELP("Move cursor to the opening curly brace in a block")
    493 		movement, { .i = VIS_MOVE_BLOCK_START }
    494 	},
    495 	[VIS_ACTION_CURSOR_BLOCK_END] = {
    496 		"vis-motion-block-end",
    497 		VIS_HELP("Move cursor to the closing curly brace in a block")
    498 		movement, { .i = VIS_MOVE_BLOCK_END }
    499 	},
    500 	[VIS_ACTION_CURSOR_PARENTHESIS_START] = {
    501 		"vis-motion-parenthesis-start",
    502 		VIS_HELP("Move cursor to the opening parenthesis inside a pair of parentheses")
    503 		movement, { .i = VIS_MOVE_PARENTHESIS_START }
    504 	},
    505 	[VIS_ACTION_CURSOR_PARENTHESIS_END] = {
    506 		"vis-motion-parenthesis-end",
    507 		VIS_HELP("Move cursor to the closing parenthesis inside a pair of parentheses")
    508 		movement, { .i = VIS_MOVE_PARENTHESIS_END }
    509 	},
    510 	[VIS_ACTION_CURSOR_COLUMN] = {
    511 		"vis-motion-column",
    512 		VIS_HELP("Move cursor to given column of current line")
    513 		movement, { .i = VIS_MOVE_COLUMN }
    514 	},
    515 	[VIS_ACTION_CURSOR_LINE_FIRST] = {
    516 		"vis-motion-line-first",
    517 		VIS_HELP("Move cursor to given line (defaults to first)")
    518 		gotoline, { .i = -1 }
    519 	},
    520 	[VIS_ACTION_CURSOR_LINE_LAST] = {
    521 		"vis-motion-line-last",
    522 		VIS_HELP("Move cursor to given line (defaults to last)")
    523 		gotoline, { .i = +1 }
    524 	},
    525 	[VIS_ACTION_CURSOR_WINDOW_LINE_TOP] = {
    526 		"vis-motion-window-line-top",
    527 		VIS_HELP("Move cursor to top line of the window")
    528 		movement, { .i = VIS_MOVE_WINDOW_LINE_TOP }
    529 	},
    530 	[VIS_ACTION_CURSOR_WINDOW_LINE_MIDDLE] = {
    531 		"vis-motion-window-line-middle",
    532 		VIS_HELP("Move cursor to middle line of the window")
    533 		movement, { .i = VIS_MOVE_WINDOW_LINE_MIDDLE }
    534 	},
    535 	[VIS_ACTION_CURSOR_WINDOW_LINE_BOTTOM] = {
    536 		"vis-motion-window-line-bottom",
    537 		VIS_HELP("Move cursor to bottom line of the window")
    538 		movement, { .i = VIS_MOVE_WINDOW_LINE_BOTTOM }
    539 	},
    540 	[VIS_ACTION_CURSOR_SEARCH_REPEAT_FORWARD] = {
    541 		"vis-motion-search-repeat-forward",
    542 		VIS_HELP("Move cursor to next match in forward direction")
    543 		movement, { .i = VIS_MOVE_SEARCH_REPEAT_FORWARD }
    544 	},
    545 	[VIS_ACTION_CURSOR_SEARCH_REPEAT_BACKWARD] = {
    546 		"vis-motion-search-repeat-backward",
    547 		VIS_HELP("Move cursor to previous match in backward direction")
    548 		movement, { .i = VIS_MOVE_SEARCH_REPEAT_BACKWARD }
    549 	},
    550 	[VIS_ACTION_CURSOR_SEARCH_REPEAT] = {
    551 		"vis-motion-search-repeat",
    552 		VIS_HELP("Move cursor to next match")
    553 		movement, { .i = VIS_MOVE_SEARCH_REPEAT }
    554 	},
    555 	[VIS_ACTION_CURSOR_SEARCH_REPEAT_REVERSE] = {
    556 		"vis-motion-search-repeat-reverse",
    557 		VIS_HELP("Move cursor to next match in opposite direction")
    558 		movement, { .i = VIS_MOVE_SEARCH_REPEAT_REVERSE }
    559 	},
    560 	[VIS_ACTION_CURSOR_SEARCH_WORD_FORWARD] = {
    561 		"vis-motion-search-word-forward",
    562 		VIS_HELP("Move cursor to next occurrence of the word under cursor")
    563 		movement, { .i = VIS_MOVE_SEARCH_WORD_FORWARD }
    564 	},
    565 	[VIS_ACTION_CURSOR_SEARCH_WORD_BACKWARD] = {
    566 		"vis-motion-search-word-backward",
    567 		VIS_HELP("Move cursor to previous occurrence of the word under cursor")
    568 		movement, { .i = VIS_MOVE_SEARCH_WORD_BACKWARD }
    569 	},
    570 	[VIS_ACTION_WINDOW_PAGE_UP] = {
    571 		"vis-window-page-up",
    572 		VIS_HELP("Scroll window pages backwards (upwards)")
    573 		wscroll, { .i = -PAGE }
    574 	},
    575 	[VIS_ACTION_WINDOW_HALFPAGE_UP] = {
    576 		"vis-window-halfpage-up",
    577 		VIS_HELP("Scroll window half pages backwards (upwards)")
    578 		wscroll, { .i = -PAGE_HALF }
    579 	},
    580 	[VIS_ACTION_WINDOW_PAGE_DOWN] = {
    581 		"vis-window-page-down",
    582 		VIS_HELP("Scroll window pages forwards (downwards)")
    583 		wscroll, { .i = +PAGE }
    584 	},
    585 	[VIS_ACTION_WINDOW_HALFPAGE_DOWN] = {
    586 		"vis-window-halfpage-down",
    587 		VIS_HELP("Scroll window half pages forwards (downwards)")
    588 		wscroll, { .i = +PAGE_HALF }
    589 	},
    590 	[VIS_ACTION_MODE_NORMAL] = {
    591 		"vis-mode-normal",
    592 		VIS_HELP("Enter normal mode")
    593 		switchmode, { .i = VIS_MODE_NORMAL }
    594 	},
    595 	[VIS_ACTION_MODE_NORMAL_ESCAPE] = {
    596 		"vis-mode-normal-escape",
    597 		VIS_HELP("Reset count or remove all non-primary selections")
    598 		normalmode_escape,
    599 	},
    600 	[VIS_ACTION_MODE_VISUAL] = {
    601 		"vis-mode-visual-charwise",
    602 		VIS_HELP("Enter characterwise visual mode")
    603 		switchmode, { .i = VIS_MODE_VISUAL }
    604 	},
    605 	[VIS_ACTION_MODE_VISUAL_ESCAPE] = {
    606 		"vis-mode-visual-escape",
    607 		VIS_HELP("Reset count or switch to normal mode")
    608 		visualmode_escape,
    609 	},
    610 	[VIS_ACTION_MODE_VISUAL_LINE] = {
    611 		"vis-mode-visual-linewise",
    612 		VIS_HELP("Enter linewise visual mode")
    613 		switchmode, { .i = VIS_MODE_VISUAL_LINE }
    614 	},
    615 	[VIS_ACTION_MODE_INSERT] = {
    616 		"vis-mode-insert",
    617 		VIS_HELP("Enter insert mode")
    618 		insertmode, { .i = VIS_MOVE_NOP }
    619 	},
    620 	[VIS_ACTION_MODE_REPLACE] = {
    621 		"vis-mode-replace",
    622 		VIS_HELP("Enter replace mode")
    623 		replacemode, { .i = VIS_MOVE_NOP }
    624 	},
    625 	[VIS_ACTION_DELETE_CHAR_PREV] = {
    626 		"vis-delete-char-prev",
    627 		VIS_HELP("Delete the previous character")
    628 		delete, { .i = VIS_MOVE_CHAR_PREV }
    629 	},
    630 	[VIS_ACTION_DELETE_CHAR_NEXT] = {
    631 		"vis-delete-char-next",
    632 		VIS_HELP("Delete the next character")
    633 		delete, { .i = VIS_MOVE_CHAR_NEXT }
    634 	},
    635 	[VIS_ACTION_DELETE_LINE_BEGIN] = {
    636 		"vis-delete-line-begin",
    637 		VIS_HELP("Delete until the start of the current line")
    638 		delete, { .i = VIS_MOVE_LINE_BEGIN }
    639 	},
    640 	[VIS_ACTION_DELETE_WORD_PREV] = {
    641 		"vis-delete-word-prev",
    642 		VIS_HELP("Delete the previous WORD")
    643 		delete, { .i = VIS_MOVE_WORD_START_PREV }
    644 	},
    645 	[VIS_ACTION_JUMPLIST_PREV] = {
    646 		"vis-jumplist-prev",
    647 		VIS_HELP("Go to older cursor position in jump list")
    648 		jumplist, { .i = -1 }
    649 	},
    650 	[VIS_ACTION_JUMPLIST_NEXT] = {
    651 		"vis-jumplist-next",
    652 		VIS_HELP("Go to newer cursor position in jump list")
    653 		jumplist, { .i = +1 }
    654 	},
    655 	[VIS_ACTION_JUMPLIST_SAVE] = {
    656 		"vis-jumplist-save",
    657 		VIS_HELP("Save current selections in jump list")
    658 		jumplist, { .i = 0 }
    659 	},
    660 	[VIS_ACTION_UNDO] = {
    661 		"vis-undo",
    662 		VIS_HELP("Undo last change")
    663 		undo,
    664 	},
    665 	[VIS_ACTION_REDO] = {
    666 		"vis-redo",
    667 		VIS_HELP("Redo last change")
    668 		redo,
    669 	},
    670 	[VIS_ACTION_EARLIER] = {
    671 		"vis-earlier",
    672 		VIS_HELP("Goto older text state")
    673 		earlier,
    674 	},
    675 	[VIS_ACTION_LATER] = {
    676 		"vis-later",
    677 		VIS_HELP("Goto newer text state")
    678 		later,
    679 	},
    680 	[VIS_ACTION_MACRO_RECORD] = {
    681 		"vis-macro-record",
    682 		VIS_HELP("Record macro into given register")
    683 		macro_record,
    684 	},
    685 	[VIS_ACTION_MACRO_REPLAY] = {
    686 		"vis-macro-replay",
    687 		VIS_HELP("Replay macro, execute the content of the given register")
    688 		macro_replay,
    689 	},
    690 	[VIS_ACTION_MARK] = {
    691 		"vis-mark",
    692 		VIS_HELP("Use given mark for next action")
    693 		mark,
    694 	},
    695 	[VIS_ACTION_REDRAW] = {
    696 		"vis-redraw",
    697 		VIS_HELP("Redraw current editor content")
    698 		call, { .f = vis_redraw }
    699 	},
    700 	[VIS_ACTION_REPLACE_CHAR] = {
    701 		"vis-replace-char",
    702 		VIS_HELP("Replace the character under the cursor")
    703 		replace,
    704 	},
    705 	[VIS_ACTION_TOTILL_REPEAT] = {
    706 		"vis-motion-totill-repeat",
    707 		VIS_HELP("Repeat latest to/till motion")
    708 		movement, { .i = VIS_MOVE_TOTILL_REPEAT }
    709 	},
    710 	[VIS_ACTION_TOTILL_REVERSE] = {
    711 		"vis-motion-totill-reverse",
    712 		VIS_HELP("Repeat latest to/till motion but in opposite direction")
    713 		movement, { .i = VIS_MOVE_TOTILL_REVERSE }
    714 	},
    715 	[VIS_ACTION_PROMPT_SEARCH_FORWARD] = {
    716 		"vis-search-forward",
    717 		VIS_HELP("Search forward")
    718 		prompt_show, { .s = "/" }
    719 	},
    720 	[VIS_ACTION_PROMPT_SEARCH_BACKWARD] = {
    721 		"vis-search-backward",
    722 		VIS_HELP("Search backward")
    723 		prompt_show, { .s = "?" }
    724 	},
    725 	[VIS_ACTION_TILL_LEFT] = {
    726 		"vis-motion-till-left",
    727 		VIS_HELP("Till after the occurrence of character to the left")
    728 		movement_key, { .i = VIS_MOVE_TILL_LEFT }
    729 	},
    730 	[VIS_ACTION_TILL_RIGHT] = {
    731 		"vis-motion-till-right",
    732 		VIS_HELP("Till before the occurrence of character to the right")
    733 		movement_key, { .i = VIS_MOVE_TILL_RIGHT }
    734 	},
    735 	[VIS_ACTION_TILL_LINE_LEFT] = {
    736 		"vis-motion-till-line-left",
    737 		VIS_HELP("Till after the occurrence of character to the left on the current line")
    738 		movement_key, { .i = VIS_MOVE_TILL_LINE_LEFT }
    739 	},
    740 	[VIS_ACTION_TILL_LINE_RIGHT] = {
    741 		"vis-motion-till-line-right",
    742 		VIS_HELP("Till before the occurrence of character to the right on the current line")
    743 		movement_key, { .i = VIS_MOVE_TILL_LINE_RIGHT }
    744 	},
    745 	[VIS_ACTION_TO_LEFT] = {
    746 		"vis-motion-to-left",
    747 		VIS_HELP("To the first occurrence of character to the left")
    748 		movement_key, { .i = VIS_MOVE_TO_LEFT }
    749 	},
    750 	[VIS_ACTION_TO_RIGHT] = {
    751 		"vis-motion-to-right",
    752 		VIS_HELP("To the first occurrence of character to the right")
    753 		movement_key, { .i = VIS_MOVE_TO_RIGHT }
    754 	},
    755 	[VIS_ACTION_TO_LINE_LEFT] = {
    756 		"vis-motion-to-line-left",
    757 		VIS_HELP("To the first occurrence of character to the left on the current line")
    758 		movement_key, { .i = VIS_MOVE_TO_LINE_LEFT }
    759 	},
    760 	[VIS_ACTION_TO_LINE_RIGHT] = {
    761 		"vis-motion-to-line-right",
    762 		VIS_HELP("To the first occurrence of character to the right on the current line")
    763 		movement_key, { .i = VIS_MOVE_TO_LINE_RIGHT }
    764 	},
    765 	[VIS_ACTION_REGISTER] = {
    766 		"vis-register",
    767 		VIS_HELP("Use given register for next operator")
    768 		reg,
    769 	},
    770 	[VIS_ACTION_OPERATOR_CHANGE] = {
    771 		"vis-operator-change",
    772 		VIS_HELP("Change operator")
    773 		operator, { .i = VIS_OP_CHANGE }
    774 	},
    775 	[VIS_ACTION_OPERATOR_DELETE] = {
    776 		"vis-operator-delete",
    777 		VIS_HELP("Delete operator")
    778 		operator, { .i = VIS_OP_DELETE }
    779 	},
    780 	[VIS_ACTION_OPERATOR_YANK] = {
    781 		"vis-operator-yank",
    782 		VIS_HELP("Yank operator")
    783 		operator, { .i = VIS_OP_YANK }
    784 	},
    785 	[VIS_ACTION_OPERATOR_SHIFT_LEFT] = {
    786 		"vis-operator-shift-left",
    787 		VIS_HELP("Shift left operator")
    788 		operator, { .i = VIS_OP_SHIFT_LEFT }
    789 	},
    790 	[VIS_ACTION_OPERATOR_SHIFT_RIGHT] = {
    791 		"vis-operator-shift-right",
    792 		VIS_HELP("Shift right operator")
    793 		operator, { .i = VIS_OP_SHIFT_RIGHT }
    794 	},
    795 	[VIS_ACTION_COUNT] = {
    796 		"vis-count",
    797 		VIS_HELP("Count specifier")
    798 		count,
    799 	},
    800 	[VIS_ACTION_INSERT_NEWLINE] = {
    801 		"vis-insert-newline",
    802 		VIS_HELP("Insert a line break (depending on file type)")
    803 		call, { .f = vis_insert_nl }
    804 	},
    805 	[VIS_ACTION_INSERT_TAB] = {
    806 		"vis-insert-tab",
    807 		VIS_HELP("Insert a tab (might be converted to spaces)")
    808 		call, { .f = vis_insert_tab }
    809 	},
    810 	[VIS_ACTION_INSERT_VERBATIM] = {
    811 		"vis-insert-verbatim",
    812 		VIS_HELP("Insert Unicode character based on code point")
    813 		insert_verbatim,
    814 	},
    815 	[VIS_ACTION_INSERT_REGISTER] = {
    816 		"vis-insert-register",
    817 		VIS_HELP("Insert specified register content")
    818 		insert_register,
    819 	},
    820 	[VIS_ACTION_WINDOW_NEXT] = {
    821 		"vis-window-next",
    822 		VIS_HELP("Focus next window")
    823 		call, { .f = vis_window_next }
    824 	},
    825 	[VIS_ACTION_WINDOW_PREV] = {
    826 		"vis-window-prev",
    827 		VIS_HELP("Focus previous window")
    828 		call, { .f = vis_window_prev }
    829 	},
    830 	[VIS_ACTION_APPEND_CHAR_NEXT] = {
    831 		"vis-append-char-next",
    832 		VIS_HELP("Append text after the cursor")
    833 		insertmode, { .i = VIS_MOVE_LINE_CHAR_NEXT }
    834 	},
    835 	[VIS_ACTION_APPEND_LINE_END] = {
    836 		"vis-append-line-end",
    837 		VIS_HELP("Append text after the end of the line")
    838 		insertmode, { .i = VIS_MOVE_LINE_END },
    839 	},
    840 	[VIS_ACTION_INSERT_LINE_START] = {
    841 		"vis-insert-line-start",
    842 		VIS_HELP("Insert text before the first non-blank in the line")
    843 		insertmode, { .i = VIS_MOVE_LINE_START },
    844 	},
    845 	[VIS_ACTION_OPEN_LINE_ABOVE] = {
    846 		"vis-open-line-above",
    847 		VIS_HELP("Begin a new line above the cursor")
    848 		openline, { .i = -1 }
    849 	},
    850 	[VIS_ACTION_OPEN_LINE_BELOW] = {
    851 		"vis-open-line-below",
    852 		VIS_HELP("Begin a new line below the cursor")
    853 		openline, { .i = +1 }
    854 	},
    855 	[VIS_ACTION_JOIN_LINES] = {
    856 		"vis-join-lines",
    857 		VIS_HELP("Join selected lines")
    858 		join, { .s = " " }
    859 	},
    860 	[VIS_ACTION_JOIN_LINES_TRIM] = {
    861 		"vis-join-lines-trim",
    862 		VIS_HELP("Join selected lines, remove white space")
    863 		join, { .s = "" }
    864 	},
    865 	[VIS_ACTION_PROMPT_SHOW] = {
    866 		"vis-prompt-show",
    867 		VIS_HELP("Show editor command line prompt")
    868 		prompt_show, { .s = ":" }
    869 	},
    870 	[VIS_ACTION_REPEAT] = {
    871 		"vis-repeat",
    872 		VIS_HELP("Repeat latest editor command")
    873 		repeat
    874 	},
    875 	[VIS_ACTION_SELECTION_FLIP] = {
    876 		"vis-selection-flip",
    877 		VIS_HELP("Flip selection, move cursor to other end")
    878 		selection_end,
    879 	},
    880 	[VIS_ACTION_WINDOW_REDRAW_TOP] = {
    881 		"vis-window-redraw-top",
    882 		VIS_HELP("Redraw cursor line at the top of the window")
    883 		window, { .w = view_redraw_top }
    884 	},
    885 	[VIS_ACTION_WINDOW_REDRAW_CENTER] = {
    886 		"vis-window-redraw-center",
    887 		VIS_HELP("Redraw cursor line at the center of the window")
    888 		window, { .w = view_redraw_center }
    889 	},
    890 	[VIS_ACTION_WINDOW_REDRAW_BOTTOM] = {
    891 		"vis-window-redraw-bottom",
    892 		VIS_HELP("Redraw cursor line at the bottom of the window")
    893 		window, { .w = view_redraw_bottom }
    894 	},
    895 	[VIS_ACTION_WINDOW_SLIDE_UP] = {
    896 		"vis-window-slide-up",
    897 		VIS_HELP("Slide window content upwards")
    898 		wslide, { .i = -1 }
    899 	},
    900 	[VIS_ACTION_WINDOW_SLIDE_DOWN] = {
    901 		"vis-window-slide-down",
    902 		VIS_HELP("Slide window content downwards")
    903 		wslide, { .i = +1 }
    904 	},
    905 	[VIS_ACTION_PUT_AFTER] = {
    906 		"vis-put-after",
    907 		VIS_HELP("Put text after the cursor")
    908 		operator, { .i = VIS_OP_PUT_AFTER }
    909 	},
    910 	[VIS_ACTION_PUT_BEFORE] = {
    911 		"vis-put-before",
    912 		VIS_HELP("Put text before the cursor")
    913 		operator, { .i = VIS_OP_PUT_BEFORE }
    914 	},
    915 	[VIS_ACTION_SELECTIONS_NEW_LINE_ABOVE] = {
    916 		"vis-selection-new-lines-above",
    917 		VIS_HELP("Create a new selection on the line above")
    918 		selections_new, { .i = -1 }
    919 	},
    920 	[VIS_ACTION_SELECTIONS_NEW_LINE_ABOVE_FIRST] = {
    921 		"vis-selection-new-lines-above-first",
    922 		VIS_HELP("Create a new selection on the line above the first selection")
    923 		selections_new, { .i = INT_MIN }
    924 	},
    925 	[VIS_ACTION_SELECTIONS_NEW_LINE_BELOW] = {
    926 		"vis-selection-new-lines-below",
    927 		VIS_HELP("Create a new selection on the line below")
    928 		selections_new, { .i = +1 }
    929 	},
    930 	[VIS_ACTION_SELECTIONS_NEW_LINE_BELOW_LAST] = {
    931 		"vis-selection-new-lines-below-last",
    932 		VIS_HELP("Create a new selection on the line below the last selection")
    933 		selections_new, { .i = INT_MAX }
    934 	},
    935 	[VIS_ACTION_SELECTIONS_NEW_LINES_BEGIN] = {
    936 		"vis-selection-new-lines-begin",
    937 		VIS_HELP("Create a new selection at the start of every line covered by selection")
    938 		operator, { .i = VIS_OP_CURSOR_SOL }
    939 	},
    940 	[VIS_ACTION_SELECTIONS_NEW_LINES_END] = {
    941 		"vis-selection-new-lines-end",
    942 		VIS_HELP("Create a new selection at the end of every line covered by selection")
    943 		operator, { .i = VIS_OP_CURSOR_EOL }
    944 	},
    945 	[VIS_ACTION_SELECTIONS_NEW_MATCH_ALL] = {
    946 		"vis-selection-new-match-all",
    947 		VIS_HELP("Select all regions matching the current selection")
    948 		selections_match_next, { .b = true }
    949 	},
    950 	[VIS_ACTION_SELECTIONS_NEW_MATCH_NEXT] = {
    951 		"vis-selection-new-match-next",
    952 		VIS_HELP("Select the next region matching the current selection")
    953 		selections_match_next,
    954 	},
    955 	[VIS_ACTION_SELECTIONS_NEW_MATCH_SKIP] = {
    956 		"vis-selection-new-match-skip",
    957 		VIS_HELP("Clear current selection, but select next match")
    958 		selections_match_skip,
    959 	},
    960 	[VIS_ACTION_SELECTIONS_ALIGN] = {
    961 		"vis-selections-align",
    962 		VIS_HELP("Try to align all selections on the same column")
    963 		selections_align,
    964 	},
    965 	[VIS_ACTION_SELECTIONS_ALIGN_INDENT_LEFT] = {
    966 		"vis-selections-align-indent-left",
    967 		VIS_HELP("Left-align all selections by inserting spaces")
    968 		selections_align_indent, { .i = -1 }
    969 	},
    970 	[VIS_ACTION_SELECTIONS_ALIGN_INDENT_RIGHT] = {
    971 		"vis-selections-align-indent-right",
    972 		VIS_HELP("Right-align all selections by inserting spaces")
    973 		selections_align_indent, { .i = +1 }
    974 	},
    975 	[VIS_ACTION_SELECTIONS_REMOVE_ALL] = {
    976 		"vis-selections-remove-all",
    977 		VIS_HELP("Remove all but the primary selection")
    978 		selections_clear,
    979 	},
    980 	[VIS_ACTION_SELECTIONS_REMOVE_LAST] = {
    981 		"vis-selections-remove-last",
    982 		VIS_HELP("Remove primary selection")
    983 		selections_remove,
    984 	},
    985 	[VIS_ACTION_SELECTIONS_REMOVE_COLUMN] = {
    986 		"vis-selections-remove-column",
    987 		VIS_HELP("Remove count selection column")
    988 		selections_remove_column, { .i = 1 }
    989 	},
    990 	[VIS_ACTION_SELECTIONS_REMOVE_COLUMN_EXCEPT] = {
    991 		"vis-selections-remove-column-except",
    992 		VIS_HELP("Remove all but the count selection column")
    993 		selections_remove_column_except, { .i = 1 }
    994 	},
    995 	[VIS_ACTION_SELECTIONS_PREV] = {
    996 		"vis-selection-prev",
    997 		VIS_HELP("Move to the previous selection")
    998 		selections_navigate, { .i = -PAGE_HALF }
    999 	},
   1000 	[VIS_ACTION_SELECTIONS_NEXT] = {
   1001 		"vis-selection-next",
   1002 		VIS_HELP("Move to the next selection")
   1003 		selections_navigate, { .i = +PAGE_HALF }
   1004 	},
   1005 	[VIS_ACTION_SELECTIONS_ROTATE_LEFT] = {
   1006 		"vis-selections-rotate-left",
   1007 		VIS_HELP("Rotate selections left")
   1008 		selections_rotate, { .i = -1 }
   1009 	},
   1010 	[VIS_ACTION_SELECTIONS_ROTATE_RIGHT] = {
   1011 		"vis-selections-rotate-right",
   1012 		VIS_HELP("Rotate selections right")
   1013 		selections_rotate, { .i = +1 }
   1014 	},
   1015 	[VIS_ACTION_SELECTIONS_TRIM] = {
   1016 		"vis-selections-trim",
   1017 		VIS_HELP("Remove leading and trailing white space from selections")
   1018 		selections_trim
   1019 	},
   1020 	[VIS_ACTION_SELECTIONS_SAVE] = {
   1021 		"vis-selections-save",
   1022 		VIS_HELP("Save currently active selections to mark")
   1023 		selections_save
   1024 	},
   1025 	[VIS_ACTION_SELECTIONS_RESTORE] = {
   1026 		"vis-selections-restore",
   1027 		VIS_HELP("Restore selections from mark")
   1028 		selections_restore
   1029 	},
   1030 	[VIS_ACTION_SELECTIONS_UNION] = {
   1031 		"vis-selections-union",
   1032 		VIS_HELP("Add selections from mark")
   1033 		selections_union
   1034 	},
   1035 	[VIS_ACTION_SELECTIONS_INTERSECT] = {
   1036 		"vis-selections-intersect",
   1037 		VIS_HELP("Intersect with selections from mark")
   1038 		selections_intersect
   1039 	},
   1040 	[VIS_ACTION_SELECTIONS_COMPLEMENT] = {
   1041 		"vis-selections-complement",
   1042 		VIS_HELP("Complement selections")
   1043 		selections_complement
   1044 	},
   1045 	[VIS_ACTION_SELECTIONS_MINUS] = {
   1046 		"vis-selections-minus",
   1047 		VIS_HELP("Subtract selections from mark")
   1048 		selections_minus
   1049 	},
   1050 	[VIS_ACTION_TEXT_OBJECT_WORD_OUTER] = {
   1051 		"vis-textobject-word-outer",
   1052 		VIS_HELP("A word leading and trailing whitespace included")
   1053 		textobj, { .i = VIS_TEXTOBJECT_OUTER_WORD }
   1054 	},
   1055 	[VIS_ACTION_TEXT_OBJECT_WORD_INNER] = {
   1056 		"vis-textobject-word-inner",
   1057 		VIS_HELP("A word leading and trailing whitespace excluded")
   1058 		textobj, { .i = VIS_TEXTOBJECT_INNER_WORD }
   1059 	},
   1060 	[VIS_ACTION_TEXT_OBJECT_LONGWORD_OUTER] = {
   1061 		"vis-textobject-bigword-outer",
   1062 		VIS_HELP("A WORD leading and trailing whitespace included")
   1063 		textobj, { .i = VIS_TEXTOBJECT_OUTER_LONGWORD }
   1064 	},
   1065 	[VIS_ACTION_TEXT_OBJECT_LONGWORD_INNER] = {
   1066 		"vis-textobject-bigword-inner",
   1067 		VIS_HELP("A WORD leading and trailing whitespace excluded")
   1068 		textobj, { .i = VIS_TEXTOBJECT_INNER_LONGWORD }
   1069 	},
   1070 	[VIS_ACTION_TEXT_OBJECT_SENTENCE] = {
   1071 		"vis-textobject-sentence",
   1072 		VIS_HELP("A sentence")
   1073 		textobj, { .i = VIS_TEXTOBJECT_SENTENCE }
   1074 	},
   1075 	[VIS_ACTION_TEXT_OBJECT_PARAGRAPH] = {
   1076 		"vis-textobject-paragraph",
   1077 		VIS_HELP("A paragraph")
   1078 		textobj, { .i = VIS_TEXTOBJECT_PARAGRAPH }
   1079 	},
   1080 	[VIS_ACTION_TEXT_OBJECT_PARAGRAPH_OUTER] = {
   1081 		"vis-textobject-paragraph-outer",
   1082 		VIS_HELP("A paragraph (outer variant)")
   1083 		textobj, { .i = VIS_TEXTOBJECT_PARAGRAPH_OUTER }
   1084 	},
   1085 	[VIS_ACTION_TEXT_OBJECT_SQUARE_BRACKET_OUTER] = {
   1086 		"vis-textobject-square-bracket-outer",
   1087 		VIS_HELP("[] block (outer variant)")
   1088 		textobj, { .i = VIS_TEXTOBJECT_OUTER_SQUARE_BRACKET }
   1089 	},
   1090 	[VIS_ACTION_TEXT_OBJECT_SQUARE_BRACKET_INNER] = {
   1091 		"vis-textobject-square-bracket-inner",
   1092 		VIS_HELP("[] block (inner variant)")
   1093 		textobj, { .i = VIS_TEXTOBJECT_INNER_SQUARE_BRACKET }
   1094 	},
   1095 	[VIS_ACTION_TEXT_OBJECT_PARENTHESIS_OUTER] = {
   1096 		"vis-textobject-parenthesis-outer",
   1097 		VIS_HELP("() block (outer variant)")
   1098 		textobj, { .i = VIS_TEXTOBJECT_OUTER_PARENTHESIS }
   1099 	},
   1100 	[VIS_ACTION_TEXT_OBJECT_PARENTHESIS_INNER] = {
   1101 		"vis-textobject-parenthesis-inner",
   1102 		VIS_HELP("() block (inner variant)")
   1103 		textobj, { .i = VIS_TEXTOBJECT_INNER_PARENTHESIS }
   1104 	},
   1105 	[VIS_ACTION_TEXT_OBJECT_ANGLE_BRACKET_OUTER] = {
   1106 		"vis-textobject-angle-bracket-outer",
   1107 		VIS_HELP("<> block (outer variant)")
   1108 		textobj, { .i = VIS_TEXTOBJECT_OUTER_ANGLE_BRACKET }
   1109 	},
   1110 	[VIS_ACTION_TEXT_OBJECT_ANGLE_BRACKET_INNER] = {
   1111 		"vis-textobject-angle-bracket-inner",
   1112 		VIS_HELP("<> block (inner variant)")
   1113 		textobj, { .i = VIS_TEXTOBJECT_INNER_ANGLE_BRACKET }
   1114 	},
   1115 	[VIS_ACTION_TEXT_OBJECT_CURLY_BRACKET_OUTER] = {
   1116 		"vis-textobject-curly-bracket-outer",
   1117 		VIS_HELP("{} block (outer variant)")
   1118 		textobj, { .i = VIS_TEXTOBJECT_OUTER_CURLY_BRACKET }
   1119 	},
   1120 	[VIS_ACTION_TEXT_OBJECT_CURLY_BRACKET_INNER] = {
   1121 		"vis-textobject-curly-bracket-inner",
   1122 		VIS_HELP("{} block (inner variant)")
   1123 		textobj, { .i = VIS_TEXTOBJECT_INNER_CURLY_BRACKET }
   1124 	},
   1125 	[VIS_ACTION_TEXT_OBJECT_QUOTE_OUTER] = {
   1126 		"vis-textobject-quote-outer",
   1127 		VIS_HELP("A quoted string, including the quotation marks")
   1128 		textobj, { .i = VIS_TEXTOBJECT_OUTER_QUOTE }
   1129 	},
   1130 	[VIS_ACTION_TEXT_OBJECT_QUOTE_INNER] = {
   1131 		"vis-textobject-quote-inner",
   1132 		VIS_HELP("A quoted string, excluding the quotation marks")
   1133 		textobj, { .i = VIS_TEXTOBJECT_INNER_QUOTE }
   1134 	},
   1135 	[VIS_ACTION_TEXT_OBJECT_SINGLE_QUOTE_OUTER] = {
   1136 		"vis-textobject-single-quote-outer",
   1137 		VIS_HELP("A single quoted string, including the quotation marks")
   1138 		textobj, { .i = VIS_TEXTOBJECT_OUTER_SINGLE_QUOTE }
   1139 	},
   1140 	[VIS_ACTION_TEXT_OBJECT_SINGLE_QUOTE_INNER] = {
   1141 		"vis-textobject-single-quote-inner",
   1142 		VIS_HELP("A single quoted string, excluding the quotation marks")
   1143 		textobj, { .i = VIS_TEXTOBJECT_INNER_SINGLE_QUOTE }
   1144 	},
   1145 	[VIS_ACTION_TEXT_OBJECT_BACKTICK_OUTER] = {
   1146 		"vis-textobject-backtick-outer",
   1147 		VIS_HELP("A backtick delimited string (outer variant)")
   1148 		textobj, { .i = VIS_TEXTOBJECT_OUTER_BACKTICK }
   1149 	},
   1150 	[VIS_ACTION_TEXT_OBJECT_BACKTICK_INNER] = {
   1151 		"vis-textobject-backtick-inner",
   1152 		VIS_HELP("A backtick delimited string (inner variant)")
   1153 		textobj, { .i = VIS_TEXTOBJECT_INNER_BACKTICK }
   1154 	},
   1155 	[VIS_ACTION_TEXT_OBJECT_LINE_OUTER] = {
   1156 		"vis-textobject-line-outer",
   1157 		VIS_HELP("The whole line")
   1158 		textobj, { .i = VIS_TEXTOBJECT_OUTER_LINE }
   1159 	},
   1160 	[VIS_ACTION_TEXT_OBJECT_LINE_INNER] = {
   1161 		"vis-textobject-line-inner",
   1162 		VIS_HELP("The whole line, excluding leading and trailing whitespace")
   1163 		textobj, { .i = VIS_TEXTOBJECT_INNER_LINE }
   1164 	},
   1165 	[VIS_ACTION_TEXT_OBJECT_INDENTATION] = {
   1166 		"vis-textobject-indentation",
   1167 		VIS_HELP("All adjacent lines with the same indentation level as the current one")
   1168 		textobj, { .i = VIS_TEXTOBJECT_INDENTATION }
   1169 	},
   1170 	[VIS_ACTION_TEXT_OBJECT_SEARCH_FORWARD] = {
   1171 		"vis-textobject-search-forward",
   1172 		VIS_HELP("The next search match in forward direction")
   1173 		textobj, { .i = VIS_TEXTOBJECT_SEARCH_FORWARD }
   1174 	},
   1175 	[VIS_ACTION_TEXT_OBJECT_SEARCH_BACKWARD] = {
   1176 		"vis-textobject-search-backward",
   1177 		VIS_HELP("The next search match in backward direction")
   1178 		textobj, { .i = VIS_TEXTOBJECT_SEARCH_BACKWARD }
   1179 	},
   1180 	[VIS_ACTION_UNICODE_INFO] = {
   1181 		"vis-unicode-info",
   1182 		VIS_HELP("Show Unicode codepoint(s) of character under cursor")
   1183 		unicode_info, { .i = VIS_ACTION_UNICODE_INFO  }
   1184 	},
   1185 	[VIS_ACTION_UTF8_INFO] = {
   1186 		"vis-utf8-info",
   1187 		VIS_HELP("Show UTF-8 encoded codepoint(s) of character under cursor")
   1188 		unicode_info, { .i = VIS_ACTION_UTF8_INFO }
   1189 	},
   1190 	[VIS_ACTION_NOP] = {
   1191 		"vis-nop",
   1192 		VIS_HELP("Ignore key, do nothing")
   1193 		nop,
   1194 	},
   1195 };
   1196 
   1197 #include "config.h"
   1198 
   1199 /** key bindings functions */
   1200 
   1201 static const char *nop(Vis *vis, const char *keys, const Arg *arg) {
   1202 	return keys;
   1203 }
   1204 
   1205 static const char *macro_record(Vis *vis, const char *keys, const Arg *arg) {
   1206 	if (!vis_macro_record_stop(vis)) {
   1207 		if (!keys[0])
   1208 			return NULL;
   1209 		const char *next = vis_keys_next(vis, keys);
   1210 		if (next - keys > 1)
   1211 			return next;
   1212 		enum VisRegister reg = vis_register_from(vis, keys[0]);
   1213 		vis_macro_record(vis, reg);
   1214 		keys++;
   1215 	}
   1216 	vis_draw(vis);
   1217 	return keys;
   1218 }
   1219 
   1220 static const char *macro_replay(Vis *vis, const char *keys, const Arg *arg) {
   1221 	if (!keys[0])
   1222 		return NULL;
   1223 	const char *next = vis_keys_next(vis, keys);
   1224 	if (next - keys > 1)
   1225 		return next;
   1226 	enum VisRegister reg = vis_register_from(vis, keys[0]);
   1227 	vis_macro_replay(vis, reg);
   1228 	return keys+1;
   1229 }
   1230 
   1231 static const char *suspend(Vis *vis, const char *keys, const Arg *arg) {
   1232 	ui_terminal_suspend(&vis->ui);
   1233 	return keys;
   1234 }
   1235 
   1236 static const char *repeat(Vis *vis, const char *keys, const Arg *arg) {
   1237 	vis_repeat(vis);
   1238 	return keys;
   1239 }
   1240 
   1241 static const char *selections_new(Vis *vis, const char *keys, const Arg *arg) {
   1242 	View *view = vis_view(vis);
   1243 	bool anchored = view_selections_primary_get(view)->anchored;
   1244 	VisCountIterator it = vis_count_iterator_get(vis, 1);
   1245 	while (vis_count_iterator_next(&it)) {
   1246 		Selection *sel = NULL;
   1247 		switch (arg->i) {
   1248 		case -1:
   1249 		case +1:
   1250 			sel = view_selections_primary_get(view);
   1251 			break;
   1252 		case INT_MIN:
   1253 			sel = view_selections(view);
   1254 			break;
   1255 		case INT_MAX:
   1256 			for (Selection *s = view_selections(view); s; s = view_selections_next(s))
   1257 				sel = s;
   1258 			break;
   1259 		}
   1260 
   1261 		if (!sel)
   1262 			return keys;
   1263 
   1264 		size_t oldpos = view_cursors_pos(sel);
   1265 		if (arg->i > 0)
   1266 			view_line_down(sel);
   1267 		else if (arg->i < 0)
   1268 			view_line_up(sel);
   1269 		size_t newpos = view_cursors_pos(sel);
   1270 		view_cursors_to(sel, oldpos);
   1271 		Selection *sel_new = view_selections_new(view, newpos);
   1272 		if (!sel_new) {
   1273 			if (arg->i == -1)
   1274 				sel_new = view_selections_prev(sel);
   1275 			else if (arg->i == +1)
   1276 				sel_new = view_selections_next(sel);
   1277 		}
   1278 		if (sel_new) {
   1279 			view_selections_primary_set(sel_new);
   1280 			sel_new->anchored = anchored;
   1281 		}
   1282 	}
   1283 	vis->action.count = VIS_COUNT_UNKNOWN;
   1284 	return keys;
   1285 }
   1286 
   1287 static const char *selections_align(Vis *vis, const char *keys, const Arg *arg) {
   1288 	View *view = vis_view(vis);
   1289 	Text *txt = vis_text(vis);
   1290 	int mincol = INT_MAX;
   1291 	for (Selection *s = view_selections(view); s; s = view_selections_next(s)) {
   1292 		if (!s->line)
   1293 			continue;
   1294 		if (s->col >= 0 && s->col < mincol)
   1295 			mincol = s->col;
   1296 	}
   1297 	for (Selection *s = view_selections(view); s; s = view_selections_next(s)) {
   1298 		if (view_cursors_cell_set(s, mincol) == -1) {
   1299 			size_t pos = view_cursors_pos(s);
   1300 			size_t col = text_line_width_set(txt, pos, mincol);
   1301 			view_cursors_to(s, col);
   1302 		}
   1303 	}
   1304 	return keys;
   1305 }
   1306 
   1307 static const char *selections_align_indent(Vis *vis, const char *keys, const Arg *arg) {
   1308 	View *view = vis_view(vis);
   1309 	Text *txt = vis_text(vis);
   1310 	bool left_align = arg->i < 0;
   1311 	int columns = view_selections_column_count(view);
   1312 
   1313 	for (int i = 0; i < columns; i++) {
   1314 		int mincol = INT_MAX, maxcol = 0;
   1315 		for (Selection *s = view_selections_column(view, i); s; s = view_selections_column_next(s, i)) {
   1316 			Filerange sel = view_selections_get(s);
   1317 			size_t pos = left_align ? sel.start : sel.end;
   1318 			int col = text_line_width_get(txt, pos);
   1319 			if (col < mincol)
   1320 				mincol = col;
   1321 			if (col > maxcol)
   1322 				maxcol = col;
   1323 		}
   1324 
   1325 		size_t len = maxcol - mincol;
   1326 		char *buf = malloc(len+1);
   1327 		if (!buf)
   1328 			return keys;
   1329 		memset(buf, ' ', len);
   1330 
   1331 		for (Selection *s = view_selections_column(view, i); s; s = view_selections_column_next(s, i)) {
   1332 			Filerange sel = view_selections_get(s);
   1333 			size_t pos = left_align ? sel.start : sel.end;
   1334 			size_t ipos = sel.start;
   1335 			int col = text_line_width_get(txt, pos);
   1336 			if (col < maxcol) {
   1337 				size_t off = maxcol - col;
   1338 				if (off <= len)
   1339 					text_insert(txt, ipos, buf, off);
   1340 			}
   1341 		}
   1342 
   1343 		free(buf);
   1344 	}
   1345 
   1346 	view_draw(view);
   1347 	return keys;
   1348 }
   1349 
   1350 static const char *selections_clear(Vis *vis, const char *keys, const Arg *arg) {
   1351 	View *view = vis_view(vis);
   1352 	if (view->selection_count > 1)
   1353 		view_selections_dispose_all(view);
   1354 	else
   1355 		view_selection_clear(view_selections_primary_get(view));
   1356 	return keys;
   1357 }
   1358 
   1359 static Selection *selection_new(View *view, Filerange *r, bool isprimary) {
   1360 	Text *txt = view->text;
   1361 	size_t pos = text_char_prev(txt, r->end);
   1362 	Selection *s = view_selections_new(view, pos);
   1363 	if (!s)
   1364 		return NULL;
   1365 	view_selections_set(s, r);
   1366 	s->anchored = true;
   1367 	if (isprimary)
   1368 		view_selections_primary_set(s);
   1369 	return s;
   1370 }
   1371 
   1372 static const char *selections_match_next(Vis *vis, const char *keys, const Arg *arg) {
   1373 	Text *txt = vis_text(vis);
   1374 	View *view = vis_view(vis);
   1375 	Selection *s = view_selections_primary_get(view);
   1376 	Filerange sel = view_selections_get(s);
   1377 	if (!text_range_valid(&sel))
   1378 		return keys;
   1379 
   1380 	static bool match_word;
   1381 
   1382 	if (view->selection_count == 1) {
   1383 		Filerange word = text_object_word(txt, view_cursors_pos(s));
   1384 		match_word = text_range_equal(&sel, &word);
   1385 	}
   1386 
   1387 	Filerange (*find_next)(Text *, size_t, const char *) = text_object_word_find_next;
   1388 	Filerange (*find_prev)(Text *, size_t, const char *) = text_object_word_find_prev;
   1389 	if (!match_word) {
   1390 		find_next = text_object_find_next;
   1391 		find_prev = text_object_find_prev;
   1392 	}
   1393 
   1394 	char *buf = text_bytes_alloc0(txt, sel.start, text_range_size(&sel));
   1395 	if (!buf)
   1396 		return keys;
   1397 
   1398 	bool match_all = arg->b;
   1399 	Filerange primary = sel;
   1400 
   1401 	for (;;) {
   1402 		sel = find_next(txt, sel.end, buf);
   1403 		if (!text_range_valid(&sel))
   1404 			break;
   1405 		if (selection_new(view, &sel, !match_all) && !match_all)
   1406 			goto out;
   1407 	}
   1408 
   1409 	sel = primary;
   1410 
   1411 	for (;;) {
   1412 		sel = find_prev(txt, sel.start, buf);
   1413 		if (!text_range_valid(&sel))
   1414 			break;
   1415 		if (selection_new(view, &sel, !match_all) && !match_all)
   1416 			break;
   1417 	}
   1418 
   1419 out:
   1420 	free(buf);
   1421 	return keys;
   1422 }
   1423 
   1424 static const char *selections_match_skip(Vis *vis, const char *keys, const Arg *arg) {
   1425 	View *view = vis_view(vis);
   1426 	Selection *sel = view_selections_primary_get(view);
   1427 	keys = selections_match_next(vis, keys, arg);
   1428 	if (sel != view_selections_primary_get(view))
   1429 		view_selections_dispose(sel);
   1430 	return keys;
   1431 }
   1432 
   1433 static const char *selections_remove(Vis *vis, const char *keys, const Arg *arg) {
   1434 	View *view = vis_view(vis);
   1435 	view_selections_dispose(view_selections_primary_get(view));
   1436 	view_cursors_to(view->selection, view_cursor_get(view));
   1437 	return keys;
   1438 }
   1439 
   1440 static const char *selections_remove_column(Vis *vis, const char *keys, const Arg *arg) {
   1441 	View *view = vis_view(vis);
   1442 	int max = view_selections_column_count(view);
   1443 	int column = VIS_COUNT_DEFAULT(vis->action.count, arg->i) - 1;
   1444 	if (column >= max)
   1445 		column = max - 1;
   1446 	if (view->selection_count == 1) {
   1447 		vis_keys_feed(vis, "<Escape>");
   1448 		return keys;
   1449 	}
   1450 
   1451 	for (Selection *s = view_selections_column(view, column), *next; s; s = next) {
   1452 		next = view_selections_column_next(s, column);
   1453 		view_selections_dispose(s);
   1454 	}
   1455 
   1456 	vis->action.count = VIS_COUNT_UNKNOWN;
   1457 	return keys;
   1458 }
   1459 
   1460 static const char *selections_remove_column_except(Vis *vis, const char *keys, const Arg *arg) {
   1461 	View *view = vis_view(vis);
   1462 	int max = view_selections_column_count(view);
   1463 	int column = VIS_COUNT_DEFAULT(vis->action.count, arg->i) - 1;
   1464 	if (column >= max)
   1465 		column = max - 1;
   1466 	if (view->selection_count == 1) {
   1467 		vis_redraw(vis);
   1468 		return keys;
   1469 	}
   1470 
   1471 	Selection *sel = view_selections(view);
   1472 	Selection *col = view_selections_column(view, column);
   1473 	for (Selection *next; sel; sel = next) {
   1474 		next = view_selections_next(sel);
   1475 		if (sel == col)
   1476 			col = view_selections_column_next(col, column);
   1477 		else
   1478 			view_selections_dispose(sel);
   1479 	}
   1480 
   1481 	vis->action.count = VIS_COUNT_UNKNOWN;
   1482 	return keys;
   1483 }
   1484 
   1485 static const char *selections_navigate(Vis *vis, const char *keys, const Arg *arg) {
   1486 	View *view = vis_view(vis);
   1487 	if (view->selection_count == 1)
   1488 		return wscroll(vis, keys, arg);
   1489 	Selection *s = view_selections_primary_get(view);
   1490 	VisCountIterator it = vis_count_iterator_get(vis, 1);
   1491 	while (vis_count_iterator_next(&it)) {
   1492 		if (arg->i > 0) {
   1493 			s = view_selections_next(s);
   1494 			if (!s)
   1495 				s = view_selections(view);
   1496 		} else {
   1497 			s = view_selections_prev(s);
   1498 			if (!s) {
   1499 				s = view_selections(view);
   1500 				for (Selection *n = s; n; n = view_selections_next(n))
   1501 					s = n;
   1502 			}
   1503 		}
   1504 	}
   1505 	view_selections_primary_set(s);
   1506 	vis->action.count = VIS_COUNT_UNKNOWN;
   1507 	return keys;
   1508 }
   1509 
   1510 static const char *selections_rotate(Vis *vis, const char *keys, const Arg *arg) {
   1511 
   1512 	typedef struct {
   1513 		Selection *sel;
   1514 		char *data;
   1515 		size_t len;
   1516 	} Rotate;
   1517 
   1518 	Array arr;
   1519 	Text *txt = vis_text(vis);
   1520 	View *view = vis_view(vis);
   1521 	int columns = view_selections_column_count(view);
   1522 	int selections = columns == 1 ? view->selection_count : columns;
   1523 	int count = VIS_COUNT_DEFAULT(vis->action.count, 1);
   1524 	array_init_sized(&arr, sizeof(Rotate));
   1525 	if (!array_reserve(&arr, selections))
   1526 		return keys;
   1527 	size_t line = 0;
   1528 
   1529 	for (Selection *s = view_selections(view), *next; s; s = next) {
   1530 		next = view_selections_next(s);
   1531 		size_t line_next = 0;
   1532 
   1533 		Filerange sel = view_selections_get(s);
   1534 		Rotate rot;
   1535 		rot.sel = s;
   1536 		rot.len = text_range_size(&sel);
   1537 		if ((rot.data = malloc(rot.len)))
   1538 			rot.len = text_bytes_get(txt, sel.start, rot.len, rot.data);
   1539 		else
   1540 			rot.len = 0;
   1541 		array_add(&arr, &rot);
   1542 
   1543 		if (!line)
   1544 			line = text_lineno_by_pos(txt, view_cursors_pos(s));
   1545 		if (next)
   1546 			line_next = text_lineno_by_pos(txt, view_cursors_pos(next));
   1547 		if (!next || (columns > 1 && line != line_next)) {
   1548 			size_t len = arr.len;
   1549 			size_t off = arg->i > 0 ? count % len : len - (count % len);
   1550 			for (size_t i = 0; i < len; i++) {
   1551 				size_t j = (i + off) % len;
   1552 				Rotate *oldrot = array_get(&arr, i);
   1553 				Rotate *newrot = array_get(&arr, j);
   1554 				if (!oldrot || !newrot || oldrot == newrot)
   1555 					continue;
   1556 				Filerange newsel = view_selections_get(newrot->sel);
   1557 				if (!text_range_valid(&newsel))
   1558 					continue;
   1559 				if (!text_delete_range(txt, &newsel))
   1560 					continue;
   1561 				if (!text_insert(txt, newsel.start, oldrot->data, oldrot->len))
   1562 					continue;
   1563 				newsel.end = newsel.start + oldrot->len;
   1564 				view_selections_set(newrot->sel, &newsel);
   1565 				free(oldrot->data);
   1566 			}
   1567 			array_clear(&arr);
   1568 		}
   1569 		line = line_next;
   1570 	}
   1571 
   1572 	array_release(&arr);
   1573 	vis->action.count = VIS_COUNT_UNKNOWN;
   1574 	return keys;
   1575 }
   1576 
   1577 static const char *selections_trim(Vis *vis, const char *keys, const Arg *arg) {
   1578 	Text *txt = vis_text(vis);
   1579 	View *view = vis_view(vis);
   1580 	for (Selection *s = view_selections(view), *next; s; s = next) {
   1581 		next = view_selections_next(s);
   1582 		Filerange sel = view_selections_get(s);
   1583 		if (!text_range_valid(&sel))
   1584 			continue;
   1585 		for (char b; sel.start < sel.end && text_byte_get(txt, sel.end-1, &b)
   1586 			&& isspace((unsigned char)b); sel.end--);
   1587 		for (char b; sel.start <= sel.end && text_byte_get(txt, sel.start, &b)
   1588 			&& isspace((unsigned char)b); sel.start++);
   1589 		if (sel.start < sel.end) {
   1590 			view_selections_set(s, &sel);
   1591 		} else if (!view_selections_dispose(s)) {
   1592 			vis_mode_switch(vis, VIS_MODE_NORMAL);
   1593 		}
   1594 	}
   1595 	return keys;
   1596 }
   1597 
   1598 static void selections_set(Vis *vis, View *view, Array *sel) {
   1599 	enum VisMode mode = vis->mode->id;
   1600 	bool anchored = mode == VIS_MODE_VISUAL || mode == VIS_MODE_VISUAL_LINE;
   1601 	view_selections_set_all(view, sel, anchored);
   1602 	if (!anchored)
   1603 		view_selections_clear_all(view);
   1604 }
   1605 
   1606 static const char *selections_save(Vis *vis, const char *keys, const Arg *arg) {
   1607 	Win *win = vis->win;
   1608 	View *view = vis_view(vis);
   1609 	enum VisMark mark = vis_mark_used(vis);
   1610 	Array sel = view_selections_get_all(view);
   1611 	vis_mark_set(win, mark, &sel);
   1612 	array_release(&sel);
   1613 	vis_cancel(vis);
   1614 	return keys;
   1615 }
   1616 
   1617 static const char *selections_restore(Vis *vis, const char *keys, const Arg *arg) {
   1618 	Win *win = vis->win;
   1619 	View *view = vis_view(vis);
   1620 	enum VisMark mark = vis_mark_used(vis);
   1621 	Array sel = vis_mark_get(win, mark);
   1622 	selections_set(vis, view, &sel);
   1623 	array_release(&sel);
   1624 	vis_cancel(vis);
   1625 	return keys;
   1626 }
   1627 
   1628 static const char *selections_union(Vis *vis, const char *keys, const Arg *arg) {
   1629 	Win *win = vis->win;
   1630 	View *view = vis_view(vis);
   1631 	enum VisMark mark = vis_mark_used(vis);
   1632 	Array a = vis_mark_get(win, mark);
   1633 	Array b = view_selections_get_all(view);
   1634 	Array sel;
   1635 	array_init_from(&sel, &a);
   1636 
   1637 	size_t i = 0, j = 0;
   1638 	Filerange *r1 = array_get(&a, i), *r2 = array_get(&b, j), cur = text_range_empty();
   1639 	while (r1 || r2) {
   1640 		if (r1 && text_range_overlap(r1, &cur)) {
   1641 			cur = text_range_union(r1, &cur);
   1642 			r1 = array_get(&a, ++i);
   1643 		} else if (r2 && text_range_overlap(r2, &cur)) {
   1644 			cur = text_range_union(r2, &cur);
   1645 			r2 = array_get(&b, ++j);
   1646 		} else {
   1647 			if (text_range_valid(&cur))
   1648 				array_add(&sel, &cur);
   1649 			if (!r1) {
   1650 				cur = *r2;
   1651 				r2 = array_get(&b, ++j);
   1652 			} else if (!r2) {
   1653 				cur = *r1;
   1654 				r1 = array_get(&a, ++i);
   1655 			} else {
   1656 				if (r1->start < r2->start) {
   1657 					cur = *r1;
   1658 					r1 = array_get(&a, ++i);
   1659 				} else {
   1660 					cur = *r2;
   1661 					r2 = array_get(&b, ++j);
   1662 				}
   1663 			}
   1664 		}
   1665 	}
   1666 
   1667 	if (text_range_valid(&cur))
   1668 		array_add(&sel, &cur);
   1669 
   1670 	selections_set(vis, view, &sel);
   1671 	vis_cancel(vis);
   1672 
   1673 	array_release(&a);
   1674 	array_release(&b);
   1675 	array_release(&sel);
   1676 
   1677 	return keys;
   1678 }
   1679 
   1680 static void intersect(Array *ret, Array *a, Array *b) {
   1681 	size_t i = 0, j = 0;
   1682 	Filerange *r1 = array_get(a, i), *r2 = array_get(b, j);
   1683 	while (r1 && r2) {
   1684 		if (text_range_overlap(r1, r2)) {
   1685 			Filerange new = text_range_intersect(r1, r2);
   1686 			array_add(ret, &new);
   1687 		}
   1688 		if (r1->end < r2->end)
   1689 			r1 = array_get(a, ++i);
   1690 		else
   1691 			r2 = array_get(b, ++j);
   1692 	}
   1693 }
   1694 
   1695 static const char *selections_intersect(Vis *vis, const char *keys, const Arg *arg) {
   1696 	Win *win = vis->win;
   1697 	View *view = vis_view(vis);
   1698 	enum VisMark mark = vis_mark_used(vis);
   1699 	Array a = vis_mark_get(win, mark);
   1700 	Array b = view_selections_get_all(view);
   1701 	Array sel;
   1702 	array_init_from(&sel, &a);
   1703 
   1704 	intersect(&sel, &a, &b);
   1705 	selections_set(vis, view, &sel);
   1706 	vis_cancel(vis);
   1707 
   1708 	array_release(&a);
   1709 	array_release(&b);
   1710 	array_release(&sel);
   1711 
   1712 	return keys;
   1713 }
   1714 
   1715 static void complement(Array *ret, Array *a, Filerange *universe) {
   1716 	size_t pos = universe->start;
   1717 	for (size_t i = 0, len = a->len; i < len; i++) {
   1718 		Filerange *r = array_get(a, i);
   1719 		if (pos < r->start) {
   1720 			Filerange new = text_range_new(pos, r->start);
   1721 			array_add(ret, &new);
   1722 		}
   1723 		pos = r->end;
   1724 	}
   1725 	if (pos < universe->end) {
   1726 		Filerange new = text_range_new(pos, universe->end);
   1727 		array_add(ret, &new);
   1728 	}
   1729 }
   1730 
   1731 static const char *selections_complement(Vis *vis, const char *keys, const Arg *arg) {
   1732 	Text *txt = vis_text(vis);
   1733 	View *view = vis_view(vis);
   1734 	Filerange universe = text_object_entire(txt, 0);
   1735 	Array a = view_selections_get_all(view);
   1736 	Array sel;
   1737 	array_init_from(&sel, &a);
   1738 
   1739 	complement(&sel, &a, &universe);
   1740 
   1741 	selections_set(vis, view, &sel);
   1742 	array_release(&a);
   1743 	array_release(&sel);
   1744 	return keys;
   1745 }
   1746 
   1747 static const char *selections_minus(Vis *vis, const char *keys, const Arg *arg) {
   1748 	Text *txt = vis_text(vis);
   1749 	Win *win = vis->win;
   1750 	View *view = vis_view(vis);
   1751 	enum VisMark mark = vis_mark_used(vis);
   1752 	Array a = view_selections_get_all(view);
   1753 	Array b = vis_mark_get(win, mark);
   1754 	Array sel;
   1755 	array_init_from(&sel, &a);
   1756 	Array b_complement;
   1757 	array_init_from(&b_complement, &b);
   1758 
   1759 	Filerange universe = text_object_entire(txt, 0);
   1760 	complement(&b_complement, &b, &universe);
   1761 	intersect(&sel, &a, &b_complement);
   1762 
   1763 	selections_set(vis, view, &sel);
   1764 	vis_cancel(vis);
   1765 
   1766 	array_release(&a);
   1767 	array_release(&b);
   1768 	array_release(&b_complement);
   1769 	array_release(&sel);
   1770 
   1771 	return keys;
   1772 }
   1773 
   1774 static const char *replace(Vis *vis, const char *keys, const Arg *arg) {
   1775 	if (!keys[0]) {
   1776 		vis_keymap_disable(vis);
   1777 		return NULL;
   1778 	}
   1779 
   1780 	const char *next = vis_keys_next(vis, keys);
   1781 	if (!next)
   1782 		return NULL;
   1783 
   1784 	char replacement[UTFmax+1];
   1785 	if (!vis_keys_utf8(vis, keys, replacement))
   1786 		return next;
   1787 
   1788 	if (replacement[0] == 0x1b) /* <Escape> */
   1789 		return next;
   1790 
   1791 	vis_operator(vis, VIS_OP_REPLACE, replacement);
   1792 	if (vis->mode->id == VIS_MODE_OPERATOR_PENDING)
   1793 		vis_motion(vis, VIS_MOVE_CHAR_NEXT);
   1794 	return next;
   1795 }
   1796 
   1797 static const char *count(Vis *vis, const char *keys, const Arg *arg) {
   1798 	int digit = keys[-1] - '0';
   1799 	int count = VIS_COUNT_DEFAULT(vis->action.count, 0);
   1800 	if (0 <= digit && digit <= 9) {
   1801 		if (digit == 0 && count == 0)
   1802 			vis_motion(vis, VIS_MOVE_LINE_BEGIN);
   1803 		else
   1804 			vis->action.count = VIS_COUNT_NORMALIZE(count * 10 + digit);
   1805 	}
   1806 	return keys;
   1807 }
   1808 
   1809 static const char *gotoline(Vis *vis, const char *keys, const Arg *arg) {
   1810 	if (vis->action.count != VIS_COUNT_UNKNOWN)
   1811 		vis_motion(vis, VIS_MOVE_LINE);
   1812 	else if (arg->i < 0)
   1813 		vis_motion(vis, VIS_MOVE_FILE_BEGIN);
   1814 	else
   1815 		vis_motion(vis, VIS_MOVE_FILE_END);
   1816 	return keys;
   1817 }
   1818 
   1819 static const char *operator(Vis *vis, const char *keys, const Arg *arg) {
   1820 	vis_operator(vis, arg->i);
   1821 	return keys;
   1822 }
   1823 
   1824 static const char *movement_key(Vis *vis, const char *keys, const Arg *arg) {
   1825 	if (!keys[0]) {
   1826 		vis_keymap_disable(vis);
   1827 		return NULL;
   1828 	}
   1829 
   1830 	const char *next = vis_keys_next(vis, keys);
   1831 	if (!next)
   1832 		return NULL;
   1833 	char utf8[UTFmax+1];
   1834 	if (vis_keys_utf8(vis, keys, utf8))
   1835 		vis_motion(vis, arg->i, utf8);
   1836 	return next;
   1837 }
   1838 
   1839 static const char *movement(Vis *vis, const char *keys, const Arg *arg) {
   1840 	vis_motion(vis, arg->i);
   1841 	return keys;
   1842 }
   1843 
   1844 static const char *textobj(Vis *vis, const char *keys, const Arg *arg) {
   1845 	vis_textobject(vis, arg->i);
   1846 	return keys;
   1847 }
   1848 
   1849 static const char *selection_end(Vis *vis, const char *keys, const Arg *arg) {
   1850 	for (Selection *s = view_selections(vis_view(vis)); s; s = view_selections_next(s))
   1851 		view_selections_flip(s);
   1852 	return keys;
   1853 }
   1854 
   1855 static const char *reg(Vis *vis, const char *keys, const Arg *arg) {
   1856 	if (!keys[0])
   1857 		return NULL;
   1858 	const char *next = vis_keys_next(vis, keys);
   1859 	if (next - keys > 1)
   1860 		return next;
   1861 	enum VisRegister reg = vis_register_from(vis, keys[0]);
   1862 	vis_register(vis, reg);
   1863 	return keys+1;
   1864 }
   1865 
   1866 static const char *mark(Vis *vis, const char *keys, const Arg *arg) {
   1867 	if (!keys[0])
   1868 		return NULL;
   1869 	const char *next = vis_keys_next(vis, keys);
   1870 	if (next - keys > 1)
   1871 		return next;
   1872 	enum VisMark mark = vis_mark_from(vis, keys[0]);
   1873 	vis_mark(vis, mark);
   1874 	return keys+1;
   1875 }
   1876 
   1877 static const char *undo(Vis *vis, const char *keys, const Arg *arg) {
   1878 	size_t pos = text_undo(vis_text(vis));
   1879 	if (pos != EPOS) {
   1880 		View *view = vis_view(vis);
   1881 		if (view->selection_count == 1)
   1882 			view_cursors_to(view->selection, pos);
   1883 		/* redraw all windows in case some display the same file */
   1884 		vis_draw(vis);
   1885 	}
   1886 	return keys;
   1887 }
   1888 
   1889 static const char *redo(Vis *vis, const char *keys, const Arg *arg) {
   1890 	size_t pos = text_redo(vis_text(vis));
   1891 	if (pos != EPOS) {
   1892 		View *view = vis_view(vis);
   1893 		if (view->selection_count == 1)
   1894 			view_cursors_to(view->selection, pos);
   1895 		/* redraw all windows in case some display the same file */
   1896 		vis_draw(vis);
   1897 	}
   1898 	return keys;
   1899 }
   1900 
   1901 static const char *earlier(Vis *vis, const char *keys, const Arg *arg) {
   1902 	size_t pos = EPOS;
   1903 	VisCountIterator it = vis_count_iterator_get(vis, 1);
   1904 	while (vis_count_iterator_next(&it))
   1905 		pos = text_earlier(vis_text(vis));
   1906 	if (pos != EPOS) {
   1907 		view_cursors_to(vis_view(vis)->selection, pos);
   1908 		/* redraw all windows in case some display the same file */
   1909 		vis_draw(vis);
   1910 	}
   1911 	return keys;
   1912 }
   1913 
   1914 static const char *later(Vis *vis, const char *keys, const Arg *arg) {
   1915 	size_t pos = EPOS;
   1916 	VisCountIterator it = vis_count_iterator_get(vis, 1);
   1917 	while (vis_count_iterator_next(&it))
   1918 		pos = text_later(vis_text(vis));
   1919 	if (pos != EPOS) {
   1920 		view_cursors_to(vis_view(vis)->selection, pos);
   1921 		/* redraw all windows in case some display the same file */
   1922 		vis_draw(vis);
   1923 	}
   1924 	return keys;
   1925 }
   1926 
   1927 static const char *delete(Vis *vis, const char *keys, const Arg *arg) {
   1928 	vis_operator(vis, VIS_OP_DELETE);
   1929 	vis_motion(vis, arg->i);
   1930 	return keys;
   1931 }
   1932 
   1933 static const char *insert_register(Vis *vis, const char *keys, const Arg *arg) {
   1934 	if (!keys[0])
   1935 		return NULL;
   1936 	const char *next = vis_keys_next(vis, keys);
   1937 	if (next - keys > 1)
   1938 		return next;
   1939 	enum VisRegister reg = vis_register_from(vis, keys[0]);
   1940 	if (reg != VIS_REG_INVALID) {
   1941 		vis_register(vis, reg);
   1942 		vis_operator(vis, VIS_OP_PUT_BEFORE_END);
   1943 	}
   1944 	return keys+1;
   1945 }
   1946 
   1947 static const char *prompt_show(Vis *vis, const char *keys, const Arg *arg) {
   1948 	vis_prompt_show(vis, arg->s);
   1949 	return keys;
   1950 }
   1951 
   1952 static const char *insert_verbatim(Vis *vis, const char *keys, const Arg *arg) {
   1953 	Rune rune = 0;
   1954 	char buf[4], type = keys[0];
   1955 	const char *data = NULL;
   1956 	int len = 0, count = 0, base = 0;
   1957 	switch (type) {
   1958 	case '\0':
   1959 		return NULL;
   1960 	case 'o':
   1961 	case 'O':
   1962 		count = 3;
   1963 		base = 8;
   1964 		break;
   1965 	case 'U':
   1966 		count = 4;
   1967 		/* fall through */
   1968 	case 'u':
   1969 		count += 4;
   1970 		base = 16;
   1971 		break;
   1972 	case 'x':
   1973 	case 'X':
   1974 		count = 2;
   1975 		base = 16;
   1976 		break;
   1977 	default:
   1978 		if ('0' <= type && type <= '9') {
   1979 			rune = type - '0';
   1980 			count = 2;
   1981 			base = 10;
   1982 		}
   1983 		break;
   1984 	}
   1985 
   1986 	if (base) {
   1987 		for (keys++; keys[0] && count > 0; keys++, count--) {
   1988 			int v = 0;
   1989 			if (base == 8 && '0' <= keys[0] && keys[0] <= '7') {
   1990 				v = keys[0] - '0';
   1991 			} else if ((base == 10 || base == 16) && '0' <= keys[0] && keys[0] <= '9') {
   1992 				v = keys[0] - '0';
   1993 			} else if (base == 16 && 'a' <= keys[0] && keys[0] <= 'f') {
   1994 				v = 10 + keys[0] - 'a';
   1995 			} else if (base == 16 && 'A' <= keys[0] && keys[0] <= 'F') {
   1996 				v = 10 + keys[0] - 'A';
   1997 			} else {
   1998 				count = 0;
   1999 				break;
   2000 			}
   2001 			rune = rune * base + v;
   2002 		}
   2003 
   2004 		if (count > 0)
   2005 			return NULL;
   2006 		if (type == 'u' || type == 'U') {
   2007 			len = runetochar(buf, &rune);
   2008 		} else {
   2009 			buf[0] = rune;
   2010 			len = 1;
   2011 		}
   2012 
   2013 		data = buf;
   2014 	} else {
   2015 		const char *next = vis_keys_next(vis, keys);
   2016 		if (!next)
   2017 			return NULL;
   2018 		if ((rune = vis_keys_codepoint(vis, keys)) != (Rune)-1) {
   2019 			len = runetochar(buf, &rune);
   2020 			if (buf[0] == '\n')
   2021 				buf[0] = '\r';
   2022 			data = buf;
   2023 		} else {
   2024 			vis_info_show(vis, "Unknown key");
   2025 		}
   2026 		keys = next;
   2027 	}
   2028 
   2029 	if (len > 0)
   2030 		vis_insert_key(vis, data, len);
   2031 	return keys;
   2032 }
   2033 
   2034 static const char *wscroll(Vis *vis, const char *keys, const Arg *arg) {
   2035 	View *view = vis_view(vis);
   2036 	int count = vis->action.count;
   2037 	switch (arg->i) {
   2038 	case -PAGE:
   2039 		view_scroll_page_up(view);
   2040 		break;
   2041 	case +PAGE:
   2042 		view_scroll_page_down(view);
   2043 		break;
   2044 	case -PAGE_HALF:
   2045 		view_scroll_halfpage_up(view);
   2046 		break;
   2047 	case +PAGE_HALF:
   2048 		view_scroll_halfpage_down(view);
   2049 		break;
   2050 	default:
   2051 		if (count == VIS_COUNT_UNKNOWN)
   2052 			count = arg->i < 0 ? -arg->i : arg->i;
   2053 		if (arg->i < 0)
   2054 			view_scroll_up(view, count);
   2055 		else
   2056 			view_scroll_down(view, count);
   2057 		break;
   2058 	}
   2059 	vis->action.count = VIS_COUNT_UNKNOWN;
   2060 	return keys;
   2061 }
   2062 
   2063 static const char *wslide(Vis *vis, const char *keys, const Arg *arg) {
   2064 	View *view = vis_view(vis);
   2065 	int count = vis->action.count;
   2066 	if (count == VIS_COUNT_UNKNOWN)
   2067 		count = arg->i < 0 ? -arg->i : arg->i;
   2068 	if (arg->i >= 0)
   2069 		view_slide_down(view, count);
   2070 	else
   2071 		view_slide_up(view, count);
   2072 	vis->action.count = VIS_COUNT_UNKNOWN;
   2073 	return keys;
   2074 }
   2075 
   2076 static const char *call(Vis *vis, const char *keys, const Arg *arg) {
   2077 	arg->f(vis);
   2078 	return keys;
   2079 }
   2080 
   2081 static const char *window(Vis *vis, const char *keys, const Arg *arg) {
   2082 	arg->w(vis_view(vis));
   2083 	return keys;
   2084 }
   2085 
   2086 static const char *openline(Vis *vis, const char *keys, const Arg *arg) {
   2087 	vis_operator(vis, VIS_OP_MODESWITCH, VIS_MODE_INSERT);
   2088 	if (arg->i > 0) {
   2089 		vis_motion(vis, VIS_MOVE_LINE_END);
   2090 		vis_keys_feed(vis, "<Enter>");
   2091 	} else {
   2092 		if (vis->autoindent) {
   2093 			vis_motion(vis, VIS_MOVE_LINE_START);
   2094 			vis_keys_feed(vis, "<vis-motion-line-start>");
   2095 		} else {
   2096 			vis_motion(vis, VIS_MOVE_LINE_BEGIN);
   2097 			vis_keys_feed(vis, "<vis-motion-line-begin>");
   2098 		}
   2099 		vis_keys_feed(vis, "<Enter><vis-motion-line-up>");
   2100 	}
   2101 	return keys;
   2102 }
   2103 
   2104 static const char *join(Vis *vis, const char *keys, const Arg *arg) {
   2105 	bool normal = (vis->mode->id == VIS_MODE_NORMAL);
   2106 	vis_operator(vis, VIS_OP_JOIN, arg->s);
   2107 	if (normal) {
   2108 		vis->action.count = VIS_COUNT_DEFAULT(vis->action.count, 0);
   2109 		if (vis->action.count > 0)
   2110 			vis->action.count -= 1;
   2111 		vis_motion(vis, VIS_MOVE_LINE_NEXT);
   2112 	}
   2113 	return keys;
   2114 }
   2115 
   2116 static const char *normalmode_escape(Vis *vis, const char *keys, const Arg *arg) {
   2117 	if (vis->action.count == VIS_COUNT_UNKNOWN)
   2118 		selections_clear(vis, keys, arg);
   2119 	else
   2120 		vis->action.count = VIS_COUNT_UNKNOWN;
   2121 	return keys;
   2122 }
   2123 
   2124 static const char *visualmode_escape(Vis *vis, const char *keys, const Arg *arg) {
   2125 	if (vis->action.count == VIS_COUNT_UNKNOWN)
   2126 		vis_mode_switch(vis, VIS_MODE_NORMAL);
   2127 	else
   2128 		vis->action.count = VIS_COUNT_UNKNOWN;
   2129 	return keys;
   2130 }
   2131 
   2132 static const char *switchmode(Vis *vis, const char *keys, const Arg *arg) {
   2133 	vis_mode_switch(vis, arg->i);
   2134 	return keys;
   2135 }
   2136 
   2137 static const char *insertmode(Vis *vis, const char *keys, const Arg *arg) {
   2138 	vis_operator(vis, VIS_OP_MODESWITCH, VIS_MODE_INSERT);
   2139 	vis_motion(vis, arg->i);
   2140 	return keys;
   2141 }
   2142 
   2143 static const char *replacemode(Vis *vis, const char *keys, const Arg *arg) {
   2144 	vis_operator(vis, VIS_OP_MODESWITCH, VIS_MODE_REPLACE);
   2145 	vis_motion(vis, arg->i);
   2146 	return keys;
   2147 }
   2148 
   2149 static const char *unicode_info(Vis *vis, const char *keys, const Arg *arg) {
   2150 	View *view = vis_view(vis);
   2151 	Text *txt = vis_text(vis);
   2152 	size_t start = view_cursor_get(view);
   2153 	size_t end = text_char_next(txt, start);
   2154 	char *grapheme = text_bytes_alloc0(txt, start, end-start), *codepoint = grapheme;
   2155 	if (!grapheme)
   2156 		return keys;
   2157 	Buffer info = {0};
   2158 	mbstate_t ps = {0};
   2159 	Iterator it = text_iterator_get(txt, start);
   2160 	for (size_t pos = start; it.pos < end; pos = it.pos) {
   2161 		if (!text_iterator_codepoint_next(&it, NULL)) {
   2162 			vis_info_show(vis, "Failed to parse code point");
   2163 			goto err;
   2164 		}
   2165 		size_t len = it.pos - pos;
   2166 		wchar_t wc = 0xFFFD;
   2167 		size_t res = mbrtowc(&wc, codepoint, len, &ps);
   2168 		bool combining = false;
   2169 		if (res != (size_t)-1 && res != (size_t)-2)
   2170 			combining = (wc != L'\0' && wcwidth(wc) == 0);
   2171 		unsigned char ch = *codepoint;
   2172 		if (ch < 128 && !isprint(ch))
   2173 			buffer_appendf(&info, "<^%c> ", ch == 127 ? '?' : ch + 64);
   2174 		else
   2175 			buffer_appendf(&info, "<%s%.*s> ", combining ? " " : "", (int)len, codepoint);
   2176 		if (arg->i == VIS_ACTION_UNICODE_INFO) {
   2177 			buffer_appendf(&info, "U+%04"PRIX32" ", (uint32_t)wc);
   2178 		} else {
   2179 			for (size_t i = 0; i < len; i++)
   2180 				buffer_appendf(&info, "%02x ", (uint8_t)codepoint[i]);
   2181 		}
   2182 		codepoint += len;
   2183 	}
   2184 	vis_info_show(vis, "%s", buffer_content0(&info));
   2185 err:
   2186 	free(grapheme);
   2187 	buffer_release(&info);
   2188 	return keys;
   2189 }
   2190 
   2191 static const char *percent(Vis *vis, const char *keys, const Arg *arg) {
   2192 	if (vis->action.count == VIS_COUNT_UNKNOWN)
   2193 		vis_motion(vis, VIS_MOVE_BRACKET_MATCH);
   2194 	else
   2195 		vis_motion(vis, VIS_MOVE_PERCENT);
   2196 	return keys;
   2197 }
   2198 
   2199 static const char *jumplist(Vis *vis, const char *keys, const Arg *arg) {
   2200 	if (arg->i < 0)
   2201 		vis_jumplist_prev(vis);
   2202 	else if (arg->i > 0)
   2203 		vis_jumplist_next(vis);
   2204 	else
   2205 		vis_jumplist_save(vis);
   2206 	return keys;
   2207 }
   2208 
   2209 static Vis *vis;
   2210 
   2211 static void signal_handler(int signum, siginfo_t *siginfo, void *context) {
   2212 	vis_signal_handler(vis, signum, siginfo, context);
   2213 }
   2214 
   2215 int main(int argc, char *argv[]) {
   2216 	for (int i = 1; i < argc; i++) {
   2217 		if (argv[i][0] != '-') {
   2218 			continue;
   2219 		} else if (strcmp(argv[i], "-") == 0) {
   2220 			continue;
   2221 		} else if (strcmp(argv[i], "--") == 0) {
   2222 			break;
   2223 		} else if (strcmp(argv[i], "-v") == 0) {
   2224 			printf("vis %s%s%s%s%s%s%s\n", VERSION,
   2225 			       CONFIG_CURSES  ? " +curses"  : "",
   2226 			       CONFIG_LUA     ? " +lua"     : "",
   2227 			       CONFIG_LPEG    ? " +lpeg"    : "",
   2228 			       CONFIG_TRE     ? " +tre"     : "",
   2229 			       CONFIG_ACL     ? " +acl"     : "",
   2230 			       CONFIG_SELINUX ? " +selinux" : "");
   2231 			return 0;
   2232 		} else {
   2233 			fprintf(stderr, "Unknown command option: %s\n", argv[i]);
   2234 			return 1;
   2235 		}
   2236 	}
   2237 
   2238 	vis = vis_new();
   2239 	if (!vis)
   2240 		return EXIT_FAILURE;
   2241 
   2242 	vis_event_emit(vis, VIS_EVENT_INIT);
   2243 
   2244 	for (int i = 0; i < LENGTH(vis_action); i++) {
   2245 		const KeyAction *action = &vis_action[i];
   2246 		if (!vis_action_register(vis, action))
   2247 			vis_die(vis, "Could not register action: %s\n", action->name);
   2248 	}
   2249 
   2250 	for (int i = 0; i < LENGTH(default_bindings); i++) {
   2251 		for (const KeyBinding **binding = default_bindings[i]; binding && *binding; binding++) {
   2252 			for (const KeyBinding *kb = *binding; kb->key; kb++) {
   2253 				vis_mode_map(vis, i, false, kb->key, kb);
   2254 			}
   2255 		}
   2256 	}
   2257 
   2258 	for (const char **k = keymaps; k[0]; k += 2)
   2259 		vis_keymap_add(vis, k[0], k[1]);
   2260 
   2261 	/* install signal handlers etc. */
   2262 	struct sigaction sa;
   2263 	memset(&sa, 0, sizeof sa);
   2264 	sigfillset(&sa.sa_mask);
   2265 	sa.sa_flags = SA_SIGINFO;
   2266 	sa.sa_sigaction = signal_handler;
   2267 	if (sigaction(SIGBUS, &sa, NULL) == -1 ||
   2268 	    sigaction(SIGINT, &sa, NULL) == -1 ||
   2269 	    sigaction(SIGCONT, &sa, NULL) == -1 ||
   2270 	    sigaction(SIGWINCH, &sa, NULL) == -1 ||
   2271 	    sigaction(SIGTERM, &sa, NULL) == -1 ||
   2272 	    sigaction(SIGHUP, &sa, NULL) == -1) {
   2273 		vis_die(vis, "Failed to set signal handler: %s\n", strerror(errno));
   2274 	}
   2275 
   2276 	sa.sa_handler = SIG_IGN;
   2277 	if (sigaction(SIGPIPE, &sa, NULL) == -1 || sigaction(SIGQUIT, &sa, NULL) == -1)
   2278 		vis_die(vis, "Failed to ignore signals\n");
   2279 
   2280 	sigset_t blockset;
   2281 	sigemptyset(&blockset);
   2282 	sigaddset(&blockset, SIGBUS);
   2283 	sigaddset(&blockset, SIGCONT);
   2284 	sigaddset(&blockset, SIGWINCH);
   2285 	sigaddset(&blockset, SIGTERM);
   2286 	sigaddset(&blockset, SIGHUP);
   2287 	if (sigprocmask(SIG_BLOCK, &blockset, NULL) == -1)
   2288 		vis_die(vis, "Failed to block signals\n");
   2289 
   2290 	char *cmd = NULL;
   2291 	bool end_of_options = false, win_created = false;
   2292 
   2293 	for (int i = 1; i < argc; i++) {
   2294 		if (argv[i][0] == '-' && !end_of_options) {
   2295 			if (strcmp(argv[i], "-") == 0) {
   2296 				if (!vis_window_new_fd(vis, STDOUT_FILENO))
   2297 					vis_die(vis, "Can not create empty buffer\n");
   2298 				ssize_t len = 0;
   2299 				char buf[PIPE_BUF];
   2300 				Text *txt = vis_text(vis);
   2301 				while ((len = read(STDIN_FILENO, buf, sizeof buf)) > 0)
   2302 					text_insert(txt, text_size(txt), buf, len);
   2303 				if (len == -1)
   2304 					vis_die(vis, "Can not read from stdin\n");
   2305 				text_snapshot(txt);
   2306 				int fd = open("/dev/tty", O_RDWR);
   2307 				if (fd == -1)
   2308 					vis_die(vis, "Can not reopen stdin\n");
   2309 				dup2(fd, STDIN_FILENO);
   2310 				close(fd);
   2311 			} else if (strcmp(argv[i], "--") == 0) {
   2312 				end_of_options = true;
   2313 				continue;
   2314 			}
   2315 		} else if (argv[i][0] == '+' && !end_of_options) {
   2316 			cmd = argv[i] + (argv[i][1] == '/' || argv[i][1] == '?');
   2317 			continue;
   2318 		} else if (!vis_window_new(vis, argv[i])) {
   2319 			vis_die(vis, "Can not load '%s': %s\n", argv[i], strerror(errno));
   2320 		}
   2321 		win_created = true;
   2322 		if (cmd) {
   2323 			vis_prompt_cmd(vis, cmd);
   2324 			cmd = NULL;
   2325 		}
   2326 	}
   2327 
   2328 	if (!vis->win && !win_created) {
   2329 		if (!vis_window_new(vis, NULL))
   2330 			vis_die(vis, "Can not create empty buffer\n");
   2331 		if (cmd)
   2332 			vis_prompt_cmd(vis, cmd);
   2333 	}
   2334 
   2335 	int status = vis_run(vis);
   2336 	vis_free(vis);
   2337 	return status;
   2338 }