vis

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

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

vis-motions.c

(15336B)


      1 #include <stdio.h>
      2 #include <string.h>
      3 #include <ctype.h>
      4 #include "vis-core.h"
      5 #include "text-motions.h"
      6 #include "text-objects.h"
      7 #include "text-util.h"
      8 #include "util.h"
      9 
     10 static Regex *search_word(Vis *vis, Text *txt, size_t pos) {
     11 	char expr[512];
     12 	Filerange word = text_object_word(txt, pos);
     13 	if (!text_range_valid(&word))
     14 		return NULL;
     15 	char *buf = text_bytes_alloc0(txt, word.start, text_range_size(&word));
     16 	if (!buf)
     17 		return NULL;
     18 	snprintf(expr, sizeof(expr), "[[:<:]]%s[[:>:]]", buf);
     19 	Regex *regex = vis_regex(vis, expr);
     20 	if (!regex) {
     21 		snprintf(expr, sizeof(expr), "\\<%s\\>", buf);
     22 		regex = vis_regex(vis, expr);
     23 	}
     24 	free(buf);
     25 	return regex;
     26 }
     27 
     28 static size_t search_word_forward(Vis *vis, Text *txt, size_t pos) {
     29 	Regex *regex = search_word(vis, txt, pos);
     30 	if (regex) {
     31 		vis->search_direction = VIS_MOVE_SEARCH_REPEAT_FORWARD;
     32 		pos = text_search_forward(txt, pos, regex);
     33 	}
     34 	text_regex_free(regex);
     35 	return pos;
     36 }
     37 
     38 static size_t search_word_backward(Vis *vis, Text *txt, size_t pos) {
     39 	Regex *regex = search_word(vis, txt, pos);
     40 	if (regex) {
     41 		vis->search_direction = VIS_MOVE_SEARCH_REPEAT_BACKWARD;
     42 		pos = text_search_backward(txt, pos, regex);
     43 	}
     44 	text_regex_free(regex);
     45 	return pos;
     46 }
     47 
     48 static size_t search_forward(Vis *vis, Text *txt, size_t pos) {
     49 	Regex *regex = vis_regex(vis, NULL);
     50 	if (regex)
     51 		pos = text_search_forward(txt, pos, regex);
     52 	text_regex_free(regex);
     53 	return pos;
     54 }
     55 
     56 static size_t search_backward(Vis *vis, Text *txt, size_t pos) {
     57 	Regex *regex = vis_regex(vis, NULL);
     58 	if (regex)
     59 		pos = text_search_backward(txt, pos, regex);
     60 	text_regex_free(regex);
     61 	return pos;
     62 }
     63 
     64 static size_t common_word_next(Vis *vis, Text *txt, size_t pos,
     65                                enum VisMotion start_next, enum VisMotion end_next,
     66                                int (*isboundary)(int)) {
     67 	char c;
     68 	Iterator it = text_iterator_get(txt, pos);
     69 	if (!text_iterator_byte_get(&it, &c))
     70 		return pos;
     71 	const Movement *motion = NULL;
     72 	int count = VIS_COUNT_DEFAULT(vis->action.count, 1);
     73 	if (isspace((unsigned char)c)) {
     74 		motion = &vis_motions[start_next];
     75 	} else if (!isboundary((unsigned char)c) && text_iterator_char_next(&it, &c) && isboundary((unsigned char)c)) {
     76 		/* we are on the last character of a word */
     77 		if (count == 1) {
     78 			/* map `cw` to `cl` */
     79 			motion = &vis_motions[VIS_MOVE_CHAR_NEXT];
     80 		} else {
     81 			/* map `c{n}w` to `c{n-1}e` */
     82 			count--;
     83 			motion = &vis_motions[end_next];
     84 		}
     85 	} else {
     86 		/* map `cw` to `ce` */
     87 		motion = &vis_motions[end_next];
     88 	}
     89 
     90 	while (count--) {
     91 		if (vis->interrupted)
     92 			return pos;
     93 		size_t newpos = motion->txt(txt, pos);
     94 		if (newpos == pos)
     95 			break;
     96 		pos = newpos;
     97 	}
     98 
     99 	if (motion->type & INCLUSIVE)
    100 		pos = text_char_next(txt, pos);
    101 
    102 	return pos;
    103 }
    104 
    105 static size_t word_next(Vis *vis, Text *txt, size_t pos) {
    106 	return common_word_next(vis, txt, pos, VIS_MOVE_WORD_START_NEXT,
    107 	                        VIS_MOVE_WORD_END_NEXT, is_word_boundary);
    108 }
    109 
    110 static size_t longword_next(Vis *vis, Text *txt, size_t pos) {
    111 	return common_word_next(vis, txt, pos, VIS_MOVE_LONGWORD_START_NEXT,
    112 	                        VIS_MOVE_LONGWORD_END_NEXT, isspace);
    113 }
    114 
    115 static size_t to_right(Vis *vis, Text *txt, size_t pos) {
    116 	char c;
    117 	size_t hit = text_find_next(txt, pos+1, vis->search_char);
    118 	if (!text_byte_get(txt, hit, &c) || c != vis->search_char[0])
    119 		return pos;
    120 	return hit;
    121 }
    122 
    123 static size_t till_right(Vis *vis, Text *txt, size_t pos) {
    124 	size_t hit = to_right(vis, txt, pos+1);
    125 	if (hit != pos)
    126 		return text_char_prev(txt, hit);
    127 	return pos;
    128 }
    129 
    130 static size_t to_left(Vis *vis, Text *txt, size_t pos) {
    131 	return text_find_prev(txt, pos, vis->search_char);
    132 }
    133 
    134 static size_t till_left(Vis *vis, Text *txt, size_t pos) {
    135 	size_t hit = to_left(vis, txt, pos-1);
    136 	if (hit != pos-1)
    137 		return text_char_next(txt, hit);
    138 	return pos;
    139 }
    140 
    141 static size_t to_line_right(Vis *vis, Text *txt, size_t pos) {
    142 	char c;
    143 	if (pos == text_line_end(txt, pos))
    144 		return pos;
    145 	size_t hit = text_line_find_next(txt, pos+1, vis->search_char);
    146 	if (!text_byte_get(txt, hit, &c) || c != vis->search_char[0])
    147 		return pos;
    148 	return hit;
    149 }
    150 
    151 static size_t till_line_right(Vis *vis, Text *txt, size_t pos) {
    152 	size_t hit = to_line_right(vis, txt, pos+1);
    153 	if (pos == text_line_end(txt, pos))
    154 		return pos;
    155 	if (hit != pos)
    156 		return text_char_prev(txt, hit);
    157 	return pos;
    158 }
    159 
    160 static size_t to_line_left(Vis *vis, Text *txt, size_t pos) {
    161 	return text_line_find_prev(txt, pos, vis->search_char);
    162 }
    163 
    164 static size_t till_line_left(Vis *vis, Text *txt, size_t pos) {
    165 	if (pos == text_line_begin(txt, pos))
    166 		return pos;
    167 	size_t hit = to_line_left(vis, txt, pos-1);
    168 	if (hit != pos-1)
    169 		return text_char_next(txt, hit);
    170 	return pos;
    171 }
    172 
    173 static size_t firstline(Text *txt, size_t pos) {
    174 	return text_line_start(txt, 0);
    175 }
    176 
    177 static size_t line(Vis *vis, Text *txt, size_t pos) {
    178 	int count = VIS_COUNT_DEFAULT(vis->action.count, 1);
    179 	return text_line_start(txt, text_pos_by_lineno(txt, count));
    180 }
    181 
    182 static size_t lastline(Text *txt, size_t pos) {
    183 	pos = text_size(txt);
    184 	return text_line_start(txt, pos > 0 ? pos-1 : pos);
    185 }
    186 
    187 static size_t column(Vis *vis, Text *txt, size_t pos) {
    188 	return text_line_offset(txt, pos, VIS_COUNT_DEFAULT(vis->action.count, 0));
    189 }
    190 
    191 static size_t view_lines_top(Vis *vis, View *view) {
    192 	return view_screenline_goto(view, VIS_COUNT_DEFAULT(vis->action.count, 1));
    193 }
    194 
    195 static size_t view_lines_middle(Vis *vis, View *view) {
    196 	int h = view->height;
    197 	return view_screenline_goto(view, h/2);
    198 }
    199 
    200 static size_t view_lines_bottom(Vis *vis, View *view) {
    201 	int h = view->height;
    202 	return view_screenline_goto(view, h - VIS_COUNT_DEFAULT(vis->action.count, 0));
    203 }
    204 
    205 static size_t window_nop(Vis *vis, Win *win, size_t pos) {
    206 	return pos;
    207 }
    208 
    209 static size_t bracket_match(Text *txt, size_t pos) {
    210 	size_t hit = text_bracket_match_symbol(txt, pos, "(){}[]<>'\"`", NULL);
    211 	if (hit != pos)
    212 		return hit;
    213 	char current;
    214 	Iterator it = text_iterator_get(txt, pos);
    215 	while (text_iterator_byte_get(&it, &current)) {
    216 		switch (current) {
    217 		case '(':
    218 		case ')':
    219 		case '{':
    220 		case '}':
    221 		case '[':
    222 		case ']':
    223 		case '<':
    224 		case '>':
    225 		case '"':
    226 		case '\'':
    227 		case '`':
    228 			return it.pos;
    229 		}
    230 		text_iterator_byte_next(&it, NULL);
    231 	}
    232 	return pos;
    233 }
    234 
    235 static size_t percent(Vis *vis, Text *txt, size_t pos) {
    236 	int ratio = VIS_COUNT_DEFAULT(vis->action.count, 0);
    237 	if (ratio > 100)
    238 		ratio = 100;
    239 	return text_size(txt) * ratio / 100;
    240 }
    241 
    242 static size_t byte(Vis *vis, Text *txt, size_t pos) {
    243 	pos = VIS_COUNT_DEFAULT(vis->action.count, 0);
    244 	size_t max = text_size(txt);
    245 	return pos <= max ? pos : max;
    246 }
    247 
    248 static size_t byte_left(Vis *vis, Text *txt, size_t pos) {
    249 	size_t off = VIS_COUNT_DEFAULT(vis->action.count, 1);
    250 	return off <= pos ? pos-off : 0;
    251 }
    252 
    253 static size_t byte_right(Vis *vis, Text *txt, size_t pos) {
    254 	size_t off = VIS_COUNT_DEFAULT(vis->action.count, 1);
    255 	size_t new = pos + off;
    256 	size_t max = text_size(txt);
    257 	return new <= max && new > pos ? new : max;
    258 }
    259 
    260 void vis_motion_type(Vis *vis, enum VisMotionType type) {
    261 	vis->action.type = type;
    262 }
    263 
    264 int vis_motion_register(Vis *vis, void *data, VisMotionFunction *motion) {
    265 
    266 	Movement *move = calloc(1, sizeof *move);
    267 	if (!move)
    268 		return -1;
    269 
    270 	move->user = motion;
    271 	move->data = data;
    272 
    273 	if (array_add_ptr(&vis->motions, move))
    274 		return VIS_MOVE_LAST + vis->motions.len - 1;
    275 	free(move);
    276 	return -1;
    277 }
    278 
    279 bool vis_motion(Vis *vis, enum VisMotion motion, ...) {
    280 	va_list ap;
    281 	va_start(ap, motion);
    282 
    283 	switch (motion) {
    284 	case VIS_MOVE_WORD_START_NEXT:
    285 		if (vis->action.op == &vis_operators[VIS_OP_CHANGE])
    286 			motion = VIS_MOVE_WORD_NEXT;
    287 		break;
    288 	case VIS_MOVE_LONGWORD_START_NEXT:
    289 		if (vis->action.op == &vis_operators[VIS_OP_CHANGE])
    290 			motion = VIS_MOVE_LONGWORD_NEXT;
    291 		break;
    292 	case VIS_MOVE_SEARCH_FORWARD:
    293 	case VIS_MOVE_SEARCH_BACKWARD:
    294 	{
    295 		const char *pattern = va_arg(ap, char*);
    296 		Regex *regex = vis_regex(vis, pattern);
    297 		if (!regex) {
    298 			vis_cancel(vis);
    299 			goto err;
    300 		}
    301 		text_regex_free(regex);
    302 		if (motion == VIS_MOVE_SEARCH_FORWARD)
    303 			motion = VIS_MOVE_SEARCH_REPEAT_FORWARD;
    304 		else
    305 			motion = VIS_MOVE_SEARCH_REPEAT_BACKWARD;
    306 		vis->search_direction = motion;
    307 		break;
    308 	}
    309 	case VIS_MOVE_SEARCH_REPEAT:
    310 	case VIS_MOVE_SEARCH_REPEAT_REVERSE:
    311 	{
    312 		if (!vis->search_direction)
    313 			vis->search_direction = VIS_MOVE_SEARCH_REPEAT_FORWARD;
    314 		if (motion == VIS_MOVE_SEARCH_REPEAT) {
    315 			motion = vis->search_direction;
    316 		} else {
    317 			motion = vis->search_direction == VIS_MOVE_SEARCH_REPEAT_FORWARD ?
    318 			                                  VIS_MOVE_SEARCH_REPEAT_BACKWARD :
    319 			                                  VIS_MOVE_SEARCH_REPEAT_FORWARD;
    320 		}
    321 		break;
    322 	}
    323 	case VIS_MOVE_TO_RIGHT:
    324 	case VIS_MOVE_TO_LEFT:
    325 	case VIS_MOVE_TO_LINE_RIGHT:
    326 	case VIS_MOVE_TO_LINE_LEFT:
    327 	case VIS_MOVE_TILL_RIGHT:
    328 	case VIS_MOVE_TILL_LEFT:
    329 	case VIS_MOVE_TILL_LINE_RIGHT:
    330 	case VIS_MOVE_TILL_LINE_LEFT:
    331 	{
    332 		const char *key = va_arg(ap, char*);
    333 		if (!key)
    334 			goto err;
    335 		strncpy(vis->search_char, key, sizeof(vis->search_char));
    336 		vis->search_char[sizeof(vis->search_char)-1] = '\0';
    337 		vis->last_totill = motion;
    338 		break;
    339 	}
    340 	case VIS_MOVE_TOTILL_REPEAT:
    341 		if (!vis->last_totill)
    342 			goto err;
    343 		motion = vis->last_totill;
    344 		break;
    345 	case VIS_MOVE_TOTILL_REVERSE:
    346 		switch (vis->last_totill) {
    347 		case VIS_MOVE_TO_RIGHT:
    348 			motion = VIS_MOVE_TO_LEFT;
    349 			break;
    350 		case VIS_MOVE_TO_LEFT:
    351 			motion = VIS_MOVE_TO_RIGHT;
    352 			break;
    353 		case VIS_MOVE_TO_LINE_RIGHT:
    354 			motion = VIS_MOVE_TO_LINE_LEFT;
    355 			break;
    356 		case VIS_MOVE_TO_LINE_LEFT:
    357 			motion = VIS_MOVE_TO_LINE_RIGHT;
    358 			break;
    359 		case VIS_MOVE_TILL_RIGHT:
    360 			motion = VIS_MOVE_TILL_LEFT;
    361 			break;
    362 		case VIS_MOVE_TILL_LEFT:
    363 			motion = VIS_MOVE_TILL_RIGHT;
    364 			break;
    365 		case VIS_MOVE_TILL_LINE_RIGHT:
    366 			motion = VIS_MOVE_TILL_LINE_LEFT;
    367 			break;
    368 		case VIS_MOVE_TILL_LINE_LEFT:
    369 			motion = VIS_MOVE_TILL_LINE_RIGHT;
    370 			break;
    371 		default:
    372 			goto err;
    373 		}
    374 		break;
    375 	default:
    376 		break;
    377 	}
    378 
    379 	if (motion < LENGTH(vis_motions))
    380 		vis->action.movement = &vis_motions[motion];
    381 	else
    382 		vis->action.movement = array_get_ptr(&vis->motions, motion - VIS_MOVE_LAST);
    383 
    384 	if (!vis->action.movement)
    385 		goto err;
    386 
    387 	va_end(ap);
    388 	vis_do(vis);
    389 	return true;
    390 err:
    391 	va_end(ap);
    392 	return false;
    393 }
    394 
    395 const Movement vis_motions[] = {
    396 	[VIS_MOVE_LINE_UP] = {
    397 		.cur = view_line_up,
    398 		.type = LINEWISE|LINEWISE_INCLUSIVE,
    399 	},
    400 	[VIS_MOVE_LINE_DOWN] = {
    401 		.cur = view_line_down,
    402 		.type = LINEWISE|LINEWISE_INCLUSIVE,
    403 	},
    404 	[VIS_MOVE_SCREEN_LINE_UP] = {
    405 		.cur = view_screenline_up,
    406 	},
    407 	[VIS_MOVE_SCREEN_LINE_DOWN] = {
    408 		.cur = view_screenline_down,
    409 	},
    410 	[VIS_MOVE_SCREEN_LINE_BEGIN] = {
    411 		.cur = view_screenline_begin,
    412 		.type = CHARWISE,
    413 	},
    414 	[VIS_MOVE_SCREEN_LINE_MIDDLE] = {
    415 		.cur = view_screenline_middle,
    416 		.type = CHARWISE,
    417 	},
    418 	[VIS_MOVE_SCREEN_LINE_END] = {
    419 		.cur = view_screenline_end,
    420 		.type = CHARWISE|INCLUSIVE,
    421 	},
    422 	[VIS_MOVE_LINE_PREV] = {
    423 		.txt = text_line_prev,
    424 	},
    425 	[VIS_MOVE_LINE_BEGIN] = {
    426 		.txt = text_line_begin,
    427 		.type = IDEMPOTENT,
    428 	},
    429 	[VIS_MOVE_LINE_START] = {
    430 		.txt = text_line_start,
    431 		.type = IDEMPOTENT,
    432 	},
    433 	[VIS_MOVE_LINE_FINISH] = {
    434 		.txt = text_line_finish,
    435 		.type = INCLUSIVE|IDEMPOTENT,
    436 	},
    437 	[VIS_MOVE_LINE_END] = {
    438 		.txt = text_line_end,
    439 		.type = IDEMPOTENT,
    440 	},
    441 	[VIS_MOVE_LINE_NEXT] = {
    442 		.txt = text_line_next,
    443 	},
    444 	[VIS_MOVE_LINE] = {
    445 		.vis = line,
    446 		.type = LINEWISE|IDEMPOTENT|JUMP,
    447 	},
    448 	[VIS_MOVE_COLUMN] = {
    449 		.vis = column,
    450 		.type = CHARWISE|IDEMPOTENT,
    451 	},
    452 	[VIS_MOVE_CHAR_PREV] = {
    453 		.txt = text_char_prev,
    454 		.type = CHARWISE,
    455 	},
    456 	[VIS_MOVE_CHAR_NEXT] = {
    457 		.txt = text_char_next,
    458 		.type = CHARWISE,
    459 	},
    460 	[VIS_MOVE_LINE_CHAR_PREV] = {
    461 		.txt = text_line_char_prev,
    462 		.type = CHARWISE,
    463 	},
    464 	[VIS_MOVE_LINE_CHAR_NEXT] = {
    465 		.txt = text_line_char_next,
    466 		.type = CHARWISE,
    467 	},
    468 	[VIS_MOVE_CODEPOINT_PREV] = {
    469 		.txt = text_codepoint_prev,
    470 		.type = CHARWISE,
    471 	},
    472 	[VIS_MOVE_CODEPOINT_NEXT] = {
    473 		.txt = text_codepoint_next,
    474 		.type = CHARWISE,
    475 	},
    476 	[VIS_MOVE_WORD_NEXT] = {
    477 		.vis = word_next,
    478 		.type = CHARWISE|IDEMPOTENT,
    479 	},
    480 	[VIS_MOVE_WORD_START_PREV] = {
    481 		.txt = text_word_start_prev,
    482 		.type = CHARWISE,
    483 	},
    484 	[VIS_MOVE_WORD_START_NEXT] = {
    485 		.txt = text_word_start_next,
    486 		.type = CHARWISE,
    487 	},
    488 	[VIS_MOVE_WORD_END_PREV] = {
    489 		.txt = text_word_end_prev,
    490 		.type = CHARWISE|INCLUSIVE,
    491 	},
    492 	[VIS_MOVE_WORD_END_NEXT] = {
    493 		.txt = text_word_end_next,
    494 		.type = CHARWISE|INCLUSIVE,
    495 	},
    496 	[VIS_MOVE_LONGWORD_NEXT] = {
    497 		.vis = longword_next,
    498 		.type = CHARWISE|IDEMPOTENT,
    499 	},
    500 	[VIS_MOVE_LONGWORD_START_PREV] = {
    501 		.txt = text_longword_start_prev,
    502 		.type = CHARWISE,
    503 	},
    504 	[VIS_MOVE_LONGWORD_START_NEXT] = {
    505 		.txt = text_longword_start_next,
    506 		.type = CHARWISE,
    507 	},
    508 	[VIS_MOVE_LONGWORD_END_PREV] = {
    509 		.txt = text_longword_end_prev,
    510 		.type = CHARWISE|INCLUSIVE,
    511 	},
    512 	[VIS_MOVE_LONGWORD_END_NEXT] = {
    513 		.txt = text_longword_end_next,
    514 		.type = CHARWISE|INCLUSIVE,
    515 	},
    516 	[VIS_MOVE_SENTENCE_PREV] = {
    517 		.txt = text_sentence_prev,
    518 		.type = CHARWISE,
    519 	},
    520 	[VIS_MOVE_SENTENCE_NEXT] = {
    521 		.txt = text_sentence_next,
    522 		.type = CHARWISE,
    523 	},
    524 	[VIS_MOVE_PARAGRAPH_PREV] = {
    525 		.txt = text_paragraph_prev,
    526 		.type = LINEWISE|JUMP,
    527 	},
    528 	[VIS_MOVE_PARAGRAPH_NEXT] = {
    529 		.txt = text_paragraph_next,
    530 		.type = LINEWISE|JUMP,
    531 	},
    532 	[VIS_MOVE_BLOCK_START] = {
    533 		.txt = text_block_start,
    534 		.type = JUMP,
    535 	},
    536 	[VIS_MOVE_BLOCK_END] = {
    537 		.txt = text_block_end,
    538 		.type = JUMP,
    539 	},
    540 	[VIS_MOVE_PARENTHESIS_START] = {
    541 		.txt = text_parenthesis_start,
    542 		.type = JUMP,
    543 	},
    544 	[VIS_MOVE_PARENTHESIS_END] = {
    545 		.txt = text_parenthesis_end,
    546 		.type = JUMP,
    547 	},
    548 	[VIS_MOVE_BRACKET_MATCH] = {
    549 		.txt = bracket_match,
    550 		.type = INCLUSIVE|JUMP,
    551 	},
    552 	[VIS_MOVE_FILE_BEGIN] = {
    553 		.txt = firstline,
    554 		.type = LINEWISE|LINEWISE_INCLUSIVE|JUMP|IDEMPOTENT,
    555 	},
    556 	[VIS_MOVE_FILE_END] = {
    557 		.txt = lastline,
    558 		.type = LINEWISE|LINEWISE_INCLUSIVE|JUMP|IDEMPOTENT,
    559 	},
    560 	[VIS_MOVE_TO_LEFT] = {
    561 		.vis = to_left,
    562 		.type = COUNT_EXACT,
    563 	},
    564 	[VIS_MOVE_TO_RIGHT] = {
    565 		.vis = to_right,
    566 		.type = INCLUSIVE|COUNT_EXACT,
    567 	},
    568 	[VIS_MOVE_TO_LINE_LEFT] = {
    569 		.vis = to_line_left,
    570 		.type = COUNT_EXACT,
    571 	},
    572 	[VIS_MOVE_TO_LINE_RIGHT] = {
    573 		.vis = to_line_right,
    574 		.type = INCLUSIVE|COUNT_EXACT,
    575 	},
    576 	[VIS_MOVE_TILL_LEFT] = {
    577 		.vis = till_left,
    578 		.type = COUNT_EXACT,
    579 	},
    580 	[VIS_MOVE_TILL_RIGHT] = {
    581 		.vis = till_right,
    582 		.type = INCLUSIVE|COUNT_EXACT,
    583 	},
    584 	[VIS_MOVE_TILL_LINE_LEFT] = {
    585 		.vis = till_line_left,
    586 		.type = COUNT_EXACT,
    587 	},
    588 	[VIS_MOVE_TILL_LINE_RIGHT] = {
    589 		.vis = till_line_right,
    590 		.type = INCLUSIVE|COUNT_EXACT,
    591 	},
    592 	[VIS_MOVE_SEARCH_WORD_FORWARD] = {
    593 		.vis = search_word_forward,
    594 		.type = JUMP,
    595 	},
    596 	[VIS_MOVE_SEARCH_WORD_BACKWARD] = {
    597 		.vis = search_word_backward,
    598 		.type = JUMP,
    599 	},
    600 	[VIS_MOVE_SEARCH_REPEAT_FORWARD] = {
    601 		.vis = search_forward,
    602 		.type = JUMP,
    603 	},
    604 	[VIS_MOVE_SEARCH_REPEAT_BACKWARD] = {
    605 		.vis = search_backward,
    606 		.type = JUMP,
    607 	},
    608 	[VIS_MOVE_WINDOW_LINE_TOP] = {
    609 		.view = view_lines_top,
    610 		.type = LINEWISE|JUMP|IDEMPOTENT,
    611 	},
    612 	[VIS_MOVE_WINDOW_LINE_MIDDLE] = {
    613 		.view = view_lines_middle,
    614 		.type = LINEWISE|JUMP|IDEMPOTENT,
    615 	},
    616 	[VIS_MOVE_WINDOW_LINE_BOTTOM] = {
    617 		.view = view_lines_bottom,
    618 		.type = LINEWISE|JUMP|IDEMPOTENT,
    619 	},
    620 	[VIS_MOVE_NOP] = {
    621 		.win = window_nop,
    622 		.type = IDEMPOTENT,
    623 	},
    624 	[VIS_MOVE_PERCENT] = {
    625 		.vis = percent,
    626 		.type = IDEMPOTENT,
    627 	},
    628 	[VIS_MOVE_BYTE] = {
    629 		.vis = byte,
    630 		.type = IDEMPOTENT,
    631 	},
    632 	[VIS_MOVE_BYTE_LEFT] = {
    633 		.vis = byte_left,
    634 		.type = IDEMPOTENT,
    635 	},
    636 	[VIS_MOVE_BYTE_RIGHT] = {
    637 		.vis = byte_right,
    638 		.type = IDEMPOTENT,
    639 	},
    640 };