test: update
[vis.git] / vis-motions.c
blob6178f5a3e1d652333f62b722b41ffcc8aaff23b0
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"
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);
24 free(buf);
25 return regex;
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);
34 text_regex_free(regex);
35 return pos;
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);
44 text_regex_free(regex);
45 return pos;
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;
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;
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_get_default(vis, 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];
85 } else {
86 /* map `cw` to `ce` */
87 motion = &vis_motions[end_next];
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;
99 if (motion->type & INCLUSIVE)
100 pos = text_char_next(txt, pos);
102 return pos;
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);
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);
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;
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;
130 static size_t to_left(Vis *vis, Text *txt, size_t pos) {
131 return text_find_prev(txt, pos, vis->search_char);
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;
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;
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;
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);
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;
173 static size_t firstline(Text *txt, size_t pos) {
174 return text_line_start(txt, 0);
177 static size_t line(Vis *vis, Text *txt, size_t pos) {
178 int count = vis_count_get_default(vis, 1);
179 return text_line_start(txt, text_pos_by_lineno(txt, count));
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);
187 static size_t column(Vis *vis, Text *txt, size_t pos) {
188 return text_line_offset(txt, pos, vis_count_get_default(vis, 0));
191 static size_t view_lines_top(Vis *vis, View *view) {
192 return view_screenline_goto(view, vis_count_get_default(vis, 1));
195 static size_t view_lines_middle(Vis *vis, View *view) {
196 int h = view_height_get(view);
197 return view_screenline_goto(view, h/2);
200 static size_t view_lines_bottom(Vis *vis, View *view) {
201 int h = view_height_get(vis->win->view);
202 return view_screenline_goto(vis->win->view, h - vis_count_get_default(vis, 0));
205 static size_t window_nop(Vis *vis, Win *win, size_t pos) {
206 return pos;
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;
230 text_iterator_byte_next(&it, NULL);
232 return pos;
235 static size_t percent(Vis *vis, Text *txt, size_t pos) {
236 int ratio = vis_count_get_default(vis, 0);
237 if (ratio > 100)
238 ratio = 100;
239 return text_size(txt) * ratio / 100;
242 static size_t byte(Vis *vis, Text *txt, size_t pos) {
243 pos = vis_count_get_default(vis, 0);
244 size_t max = text_size(txt);
245 return pos <= max ? pos : max;
248 static size_t byte_left(Vis *vis, Text *txt, size_t pos) {
249 size_t off = vis_count_get_default(vis, 1);
250 return off <= pos ? pos-off : 0;
253 static size_t byte_right(Vis *vis, Text *txt, size_t pos) {
254 size_t off = vis_count_get_default(vis, 1);
255 size_t new = pos + off;
256 size_t max = text_size(txt);
257 return new <= max && new > pos ? new : max;
260 void vis_motion_type(Vis *vis, enum VisMotionType type) {
261 vis->action.type = type;
264 int vis_motion_register(Vis *vis, void *data, VisMotionFunction *motion) {
266 Movement *move = calloc(1, sizeof *move);
267 if (!move)
268 return -1;
270 move->user = motion;
271 move->data = data;
273 if (array_add_ptr(&vis->motions, move))
274 return VIS_MOVE_LAST + array_length(&vis->motions) - 1;
275 free(move);
276 return -1;
279 bool vis_motion(Vis *vis, enum VisMotion motion, ...) {
280 va_list ap;
281 va_start(ap, motion);
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:
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;
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;
309 case VIS_MOVE_SEARCH_REPEAT:
310 case VIS_MOVE_SEARCH_REPEAT_REVERSE:
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;
321 break;
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:
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;
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;
374 break;
375 default:
376 break;
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);
384 if (!vis->action.movement)
385 goto err;
387 va_end(ap);
388 vis_do(vis);
389 return true;
390 err:
391 va_end(ap);
392 return false;
395 const Movement vis_motions[] = {
396 [VIS_MOVE_LINE_UP] = {
397 .cur = view_line_up,
398 .type = LINEWISE|LINEWISE_INCLUSIVE,
400 [VIS_MOVE_LINE_DOWN] = {
401 .cur = view_line_down,
402 .type = LINEWISE|LINEWISE_INCLUSIVE,
404 [VIS_MOVE_SCREEN_LINE_UP] = {
405 .cur = view_screenline_up,
407 [VIS_MOVE_SCREEN_LINE_DOWN] = {
408 .cur = view_screenline_down,
410 [VIS_MOVE_SCREEN_LINE_BEGIN] = {
411 .cur = view_screenline_begin,
412 .type = CHARWISE,
414 [VIS_MOVE_SCREEN_LINE_MIDDLE] = {
415 .cur = view_screenline_middle,
416 .type = CHARWISE,
418 [VIS_MOVE_SCREEN_LINE_END] = {
419 .cur = view_screenline_end,
420 .type = CHARWISE|INCLUSIVE,
422 [VIS_MOVE_LINE_PREV] = {
423 .txt = text_line_prev,
425 [VIS_MOVE_LINE_BEGIN] = {
426 .txt = text_line_begin,
427 .type = IDEMPOTENT,
429 [VIS_MOVE_LINE_START] = {
430 .txt = text_line_start,
431 .type = IDEMPOTENT,
433 [VIS_MOVE_LINE_FINISH] = {
434 .txt = text_line_finish,
435 .type = INCLUSIVE|IDEMPOTENT,
437 [VIS_MOVE_LINE_END] = {
438 .txt = text_line_end,
439 .type = IDEMPOTENT,
441 [VIS_MOVE_LINE_NEXT] = {
442 .txt = text_line_next,
444 [VIS_MOVE_LINE] = {
445 .vis = line,
446 .type = LINEWISE|IDEMPOTENT|JUMP,
448 [VIS_MOVE_COLUMN] = {
449 .vis = column,
450 .type = CHARWISE|IDEMPOTENT,
452 [VIS_MOVE_CHAR_PREV] = {
453 .txt = text_char_prev,
454 .type = CHARWISE,
456 [VIS_MOVE_CHAR_NEXT] = {
457 .txt = text_char_next,
458 .type = CHARWISE,
460 [VIS_MOVE_LINE_CHAR_PREV] = {
461 .txt = text_line_char_prev,
462 .type = CHARWISE,
464 [VIS_MOVE_LINE_CHAR_NEXT] = {
465 .txt = text_line_char_next,
466 .type = CHARWISE,
468 [VIS_MOVE_CODEPOINT_PREV] = {
469 .txt = text_codepoint_prev,
470 .type = CHARWISE,
472 [VIS_MOVE_CODEPOINT_NEXT] = {
473 .txt = text_codepoint_next,
474 .type = CHARWISE,
476 [VIS_MOVE_WORD_NEXT] = {
477 .vis = word_next,
478 .type = CHARWISE|IDEMPOTENT,
480 [VIS_MOVE_WORD_START_PREV] = {
481 .txt = text_word_start_prev,
482 .type = CHARWISE,
484 [VIS_MOVE_WORD_START_NEXT] = {
485 .txt = text_word_start_next,
486 .type = CHARWISE,
488 [VIS_MOVE_WORD_END_PREV] = {
489 .txt = text_word_end_prev,
490 .type = CHARWISE|INCLUSIVE,
492 [VIS_MOVE_WORD_END_NEXT] = {
493 .txt = text_word_end_next,
494 .type = CHARWISE|INCLUSIVE,
496 [VIS_MOVE_LONGWORD_NEXT] = {
497 .vis = longword_next,
498 .type = CHARWISE|IDEMPOTENT,
500 [VIS_MOVE_LONGWORD_START_PREV] = {
501 .txt = text_longword_start_prev,
502 .type = CHARWISE,
504 [VIS_MOVE_LONGWORD_START_NEXT] = {
505 .txt = text_longword_start_next,
506 .type = CHARWISE,
508 [VIS_MOVE_LONGWORD_END_PREV] = {
509 .txt = text_longword_end_prev,
510 .type = CHARWISE|INCLUSIVE,
512 [VIS_MOVE_LONGWORD_END_NEXT] = {
513 .txt = text_longword_end_next,
514 .type = CHARWISE|INCLUSIVE,
516 [VIS_MOVE_SENTENCE_PREV] = {
517 .txt = text_sentence_prev,
518 .type = CHARWISE,
520 [VIS_MOVE_SENTENCE_NEXT] = {
521 .txt = text_sentence_next,
522 .type = CHARWISE,
524 [VIS_MOVE_PARAGRAPH_PREV] = {
525 .txt = text_paragraph_prev,
526 .type = LINEWISE|JUMP,
528 [VIS_MOVE_PARAGRAPH_NEXT] = {
529 .txt = text_paragraph_next,
530 .type = LINEWISE|JUMP,
532 [VIS_MOVE_BLOCK_START] = {
533 .txt = text_block_start,
534 .type = JUMP,
536 [VIS_MOVE_BLOCK_END] = {
537 .txt = text_block_end,
538 .type = JUMP,
540 [VIS_MOVE_PARENTHESIS_START] = {
541 .txt = text_parenthesis_start,
542 .type = JUMP,
544 [VIS_MOVE_PARENTHESIS_END] = {
545 .txt = text_parenthesis_end,
546 .type = JUMP,
548 [VIS_MOVE_BRACKET_MATCH] = {
549 .txt = bracket_match,
550 .type = INCLUSIVE|JUMP,
552 [VIS_MOVE_FILE_BEGIN] = {
553 .txt = firstline,
554 .type = LINEWISE|LINEWISE_INCLUSIVE|JUMP|IDEMPOTENT,
556 [VIS_MOVE_FILE_END] = {
557 .txt = lastline,
558 .type = LINEWISE|LINEWISE_INCLUSIVE|JUMP|IDEMPOTENT,
560 [VIS_MOVE_TO_LEFT] = {
561 .vis = to_left,
562 .type = COUNT_EXACT,
564 [VIS_MOVE_TO_RIGHT] = {
565 .vis = to_right,
566 .type = INCLUSIVE|COUNT_EXACT,
568 [VIS_MOVE_TO_LINE_LEFT] = {
569 .vis = to_line_left,
570 .type = COUNT_EXACT,
572 [VIS_MOVE_TO_LINE_RIGHT] = {
573 .vis = to_line_right,
574 .type = INCLUSIVE|COUNT_EXACT,
576 [VIS_MOVE_TILL_LEFT] = {
577 .vis = till_left,
578 .type = COUNT_EXACT,
580 [VIS_MOVE_TILL_RIGHT] = {
581 .vis = till_right,
582 .type = INCLUSIVE|COUNT_EXACT,
584 [VIS_MOVE_TILL_LINE_LEFT] = {
585 .vis = till_line_left,
586 .type = COUNT_EXACT,
588 [VIS_MOVE_TILL_LINE_RIGHT] = {
589 .vis = till_line_right,
590 .type = INCLUSIVE|COUNT_EXACT,
592 [VIS_MOVE_SEARCH_WORD_FORWARD] = {
593 .vis = search_word_forward,
594 .type = JUMP,
596 [VIS_MOVE_SEARCH_WORD_BACKWARD] = {
597 .vis = search_word_backward,
598 .type = JUMP,
600 [VIS_MOVE_SEARCH_REPEAT_FORWARD] = {
601 .vis = search_forward,
602 .type = JUMP,
604 [VIS_MOVE_SEARCH_REPEAT_BACKWARD] = {
605 .vis = search_backward,
606 .type = JUMP,
608 [VIS_MOVE_WINDOW_LINE_TOP] = {
609 .view = view_lines_top,
610 .type = LINEWISE|JUMP|IDEMPOTENT,
612 [VIS_MOVE_WINDOW_LINE_MIDDLE] = {
613 .view = view_lines_middle,
614 .type = LINEWISE|JUMP|IDEMPOTENT,
616 [VIS_MOVE_WINDOW_LINE_BOTTOM] = {
617 .view = view_lines_bottom,
618 .type = LINEWISE|JUMP|IDEMPOTENT,
620 [VIS_MOVE_NOP] = {
621 .win = window_nop,
622 .type = IDEMPOTENT,
624 [VIS_MOVE_PERCENT] = {
625 .vis = percent,
626 .type = IDEMPOTENT,
628 [VIS_MOVE_BYTE] = {
629 .vis = byte,
630 .type = IDEMPOTENT,
632 [VIS_MOVE_BYTE_LEFT] = {
633 .vis = byte_left,
634 .type = IDEMPOTENT,
636 [VIS_MOVE_BYTE_RIGHT] = {
637 .vis = byte_right,
638 .type = IDEMPOTENT,