vis: reject invalid register name when recording a macro
[vis.git] / vis-motions.c
blob0166117419cdbc6d17395a732f0652fcff8b192b
1 #include <stdio.h>
2 #include <string.h>
3 #include "vis-core.h"
4 #include "text-motions.h"
5 #include "text-objects.h"
6 #include "text-util.h"
7 #include "util.h"
9 static Regex *search_word(Vis *vis, Text *txt, size_t pos) {
10 char expr[512];
11 Filerange word = text_object_word(txt, pos);
12 if (!text_range_valid(&word))
13 return NULL;
14 char *buf = text_bytes_alloc0(txt, word.start, text_range_size(&word));
15 if (!buf)
16 return NULL;
17 snprintf(expr, sizeof(expr), "[[:<:]]%s[[:>:]]", buf);
18 Regex *regex = vis_regex(vis, expr);
19 if (!regex) {
20 snprintf(expr, sizeof(expr), "\\<%s\\>", buf);
21 regex = vis_regex(vis, expr);
23 free(buf);
24 return regex;
27 static size_t search_word_forward(Vis *vis, Text *txt, size_t pos) {
28 Regex *regex = search_word(vis, txt, pos);
29 if (regex) {
30 vis->search_direction = VIS_MOVE_SEARCH_REPEAT_FORWARD;
31 pos = text_search_forward(txt, pos, regex);
33 text_regex_free(regex);
34 return pos;
37 static size_t search_word_backward(Vis *vis, Text *txt, size_t pos) {
38 Regex *regex = search_word(vis, txt, pos);
39 if (regex) {
40 vis->search_direction = VIS_MOVE_SEARCH_REPEAT_BACKWARD;
41 pos = text_search_backward(txt, pos, regex);
43 text_regex_free(regex);
44 return pos;
47 static size_t search_forward(Vis *vis, Text *txt, size_t pos) {
48 Regex *regex = vis_regex(vis, NULL);
49 if (regex)
50 pos = text_search_forward(txt, pos, regex);
51 text_regex_free(regex);
52 return pos;
55 static size_t search_backward(Vis *vis, Text *txt, size_t pos) {
56 Regex *regex = vis_regex(vis, NULL);
57 if (regex)
58 pos = text_search_backward(txt, pos, regex);
59 text_regex_free(regex);
60 return pos;
63 static size_t common_word_next(Vis *vis, Text *txt, size_t pos, enum VisMotion end_next) {
64 char c;
65 Iterator it = text_iterator_get(txt, pos);
66 if (!text_iterator_byte_get(&it, &c))
67 return pos;
68 const Movement *motion = NULL;
69 int count = vis_count_get_default(vis, 1);
70 if (c == ' ' || c == '\t') {
71 motion = &vis_motions[VIS_MOVE_WORD_START_NEXT];
72 } else if (text_iterator_char_next(&it, &c) && (c == ' ' || c == '\t')) {
73 /* we are on the last character of a word */
74 if (count == 1) {
75 /* map `cw` to `cl` */
76 motion = &vis_motions[VIS_MOVE_CHAR_NEXT];
77 } else {
78 /* map `c{n}w` to `c{n-1}e` */
79 count--;
80 motion = &vis_motions[end_next];
82 } else {
83 /* map `cw` to `ce` */
84 motion = &vis_motions[end_next];
87 while (count--) {
88 size_t newpos = motion->txt(txt, pos);
89 if (newpos == pos)
90 break;
91 pos = newpos;
94 if (motion->type & INCLUSIVE)
95 pos = text_char_next(txt, pos);
97 return pos;
100 static size_t word_next(Vis *vis, Text *txt, size_t pos) {
101 return common_word_next(vis, txt, pos, VIS_MOVE_WORD_END_NEXT);
104 static size_t longword_next(Vis *vis, Text *txt, size_t pos) {
105 return common_word_next(vis, txt, pos, VIS_MOVE_LONGWORD_END_NEXT);
108 static size_t mark_goto(Vis *vis, File *file, size_t pos) {
109 return text_mark_get(file->text, file->marks[vis->action.mark]);
112 static size_t mark_line_goto(Vis *vis, File *file, size_t pos) {
113 return text_line_start(file->text, mark_goto(vis, file, pos));
116 static size_t to(Vis *vis, Text *txt, size_t pos) {
117 char c;
118 if (pos == text_line_end(txt, pos))
119 return pos;
120 size_t hit = text_line_find_next(txt, pos+1, vis->search_char);
121 if (!text_byte_get(txt, hit, &c) || c != vis->search_char[0])
122 return pos;
123 return hit;
126 static size_t till(Vis *vis, Text *txt, size_t pos) {
127 size_t hit = to(vis, txt, pos+1);
128 if (pos == text_line_end(txt, pos))
129 return pos;
130 if (hit != pos)
131 return text_char_prev(txt, hit);
132 return pos;
135 static size_t to_left(Vis *vis, Text *txt, size_t pos) {
136 return text_line_find_prev(txt, pos, vis->search_char);
139 static size_t till_left(Vis *vis, Text *txt, size_t pos) {
140 if (pos == text_line_begin(txt, pos))
141 return pos;
142 size_t hit = to_left(vis, txt, pos-1);
143 if (hit != pos-1)
144 return text_char_next(txt, hit);
145 return pos;
148 static size_t firstline(Text *txt, size_t pos) {
149 return text_line_start(txt, 0);
152 static size_t line(Vis *vis, Text *txt, size_t pos) {
153 int count = vis_count_get_default(vis, 1);
154 return text_line_start(txt, text_pos_by_lineno(txt, count));
157 static size_t lastline(Text *txt, size_t pos) {
158 pos = text_size(txt);
159 return text_line_start(txt, pos > 0 ? pos-1 : pos);
162 static size_t column(Vis *vis, Text *txt, size_t pos) {
163 return text_line_offset(txt, pos, vis_count_get_default(vis, 0));
166 static size_t view_lines_top(Vis *vis, View *view) {
167 return view_screenline_goto(view, vis_count_get_default(vis, 1));
170 static size_t view_lines_middle(Vis *vis, View *view) {
171 int h = view_height_get(view);
172 return view_screenline_goto(view, h/2);
175 static size_t view_lines_bottom(Vis *vis, View *view) {
176 int h = view_height_get(vis->win->view);
177 return view_screenline_goto(vis->win->view, h - vis_count_get_default(vis, 0));
180 static size_t window_changelist_next(Vis *vis, Win *win, size_t pos) {
181 ChangeList *cl = &win->changelist;
182 Text *txt = win->file->text;
183 time_t state = text_state(txt);
184 if (cl->state != state)
185 cl->index = 0;
186 else if (cl->index > 0 && pos == cl->pos)
187 cl->index--;
188 size_t newpos = text_history_get(txt, cl->index);
189 if (newpos == EPOS)
190 cl->index++;
191 else
192 cl->pos = newpos;
193 cl->state = state;
194 return cl->pos;
197 static size_t window_changelist_prev(Vis *vis, Win *win, size_t pos) {
198 ChangeList *cl = &win->changelist;
199 Text *txt = win->file->text;
200 time_t state = text_state(txt);
201 if (cl->state != state)
202 cl->index = 0;
203 else if (pos == cl->pos)
204 win->changelist.index++;
205 size_t newpos = text_history_get(txt, cl->index);
206 if (newpos == EPOS)
207 cl->index--;
208 else
209 cl->pos = newpos;
210 cl->state = state;
211 return cl->pos;
214 static size_t window_jumplist_next(Vis *vis, Win *win, size_t cur) {
215 while (win->jumplist) {
216 Mark mark = (Mark)ringbuf_next(win->jumplist);
217 if (!mark)
218 return cur;
219 size_t pos = text_mark_get(win->file->text, mark);
220 if (pos != EPOS && pos != cur)
221 return pos;
223 return cur;
226 static size_t window_jumplist_prev(Vis *vis, Win *win, size_t cur) {
227 while (win->jumplist) {
228 Mark mark = (Mark)ringbuf_prev(win->jumplist);
229 if (!mark)
230 return cur;
231 size_t pos = text_mark_get(win->file->text, mark);
232 if (pos != EPOS && pos != cur)
233 return pos;
235 return cur;
238 static size_t window_nop(Vis *vis, Win *win, size_t pos) {
239 return pos;
242 static size_t bracket_match(Text *txt, size_t pos) {
243 size_t hit = text_bracket_match_symbol(txt, pos, "(){}[]<>");
244 if (hit != pos)
245 return hit;
246 char current;
247 Iterator it = text_iterator_get(txt, pos);
248 while (text_iterator_byte_get(&it, &current)) {
249 switch (current) {
250 case '(':
251 case ')':
252 case '{':
253 case '}':
254 case '[':
255 case ']':
256 case '<':
257 case '>':
258 return it.pos;
260 text_iterator_byte_next(&it, NULL);
262 return pos;
265 static size_t percent(Vis *vis, Text *txt, size_t pos) {
266 int ratio = vis_count_get_default(vis, 0);
267 if (ratio > 100)
268 ratio = 100;
269 return text_size(txt) * ratio / 100;
272 static size_t byte(Vis *vis, Text *txt, size_t pos) {
273 pos = vis_count_get_default(vis, 0);
274 size_t max = text_size(txt);
275 return pos <= max ? pos : max;
278 static size_t byte_left(Vis *vis, Text *txt, size_t pos) {
279 size_t off = vis_count_get_default(vis, 1);
280 return off <= pos ? pos-off : 0;
283 static size_t byte_right(Vis *vis, Text *txt, size_t pos) {
284 size_t off = vis_count_get_default(vis, 1);
285 size_t new = pos + off;
286 size_t max = text_size(txt);
287 return new <= max && new > pos ? new : max;
290 void vis_motion_type(Vis *vis, enum VisMotionType type) {
291 vis->action.type = type;
294 int vis_motion_register(Vis *vis, enum VisMotionType type, void *data,
295 size_t (*motion)(Vis*, Win*, void*, size_t pos)) {
297 Movement *move = calloc(1, sizeof *move);
298 if (!move)
299 return -1;
301 move->user = motion;
302 move->type = type;
303 move->data = data;
305 if (array_add_ptr(&vis->motions, move))
306 return VIS_MOVE_LAST + array_length(&vis->motions) - 1;
307 free(move);
308 return -1;
311 bool vis_motion(Vis *vis, enum VisMotion motion, ...) {
312 va_list ap;
313 va_start(ap, motion);
315 switch (motion) {
316 case VIS_MOVE_WORD_START_NEXT:
317 if (vis->action.op == &vis_operators[VIS_OP_CHANGE])
318 motion = VIS_MOVE_WORD_NEXT;
319 break;
320 case VIS_MOVE_LONGWORD_START_NEXT:
321 if (vis->action.op == &vis_operators[VIS_OP_CHANGE])
322 motion = VIS_MOVE_LONGWORD_NEXT;
323 break;
324 case VIS_MOVE_SEARCH_FORWARD:
325 case VIS_MOVE_SEARCH_BACKWARD:
327 const char *pattern = va_arg(ap, char*);
328 Regex *regex = vis_regex(vis, pattern);
329 if (!regex) {
330 vis_cancel(vis);
331 goto err;
333 text_regex_free(regex);
334 if (motion == VIS_MOVE_SEARCH_FORWARD)
335 motion = VIS_MOVE_SEARCH_REPEAT_FORWARD;
336 else
337 motion = VIS_MOVE_SEARCH_REPEAT_BACKWARD;
338 vis->search_direction = motion;
339 break;
341 case VIS_MOVE_SEARCH_REPEAT:
342 case VIS_MOVE_SEARCH_REPEAT_REVERSE:
344 if (!vis->search_direction)
345 vis->search_direction = VIS_MOVE_SEARCH_REPEAT_FORWARD;
346 if (motion == VIS_MOVE_SEARCH_REPEAT) {
347 motion = vis->search_direction;
348 } else {
349 motion = vis->search_direction == VIS_MOVE_SEARCH_REPEAT_FORWARD ?
350 VIS_MOVE_SEARCH_REPEAT_BACKWARD :
351 VIS_MOVE_SEARCH_REPEAT_FORWARD;
353 break;
355 case VIS_MOVE_RIGHT_TO:
356 case VIS_MOVE_LEFT_TO:
357 case VIS_MOVE_RIGHT_TILL:
358 case VIS_MOVE_LEFT_TILL:
360 const char *key = va_arg(ap, char*);
361 if (!key)
362 goto err;
363 strncpy(vis->search_char, key, sizeof(vis->search_char));
364 vis->search_char[sizeof(vis->search_char)-1] = '\0';
365 vis->last_totill = motion;
366 break;
368 case VIS_MOVE_TOTILL_REPEAT:
369 if (!vis->last_totill)
370 goto err;
371 motion = vis->last_totill;
372 break;
373 case VIS_MOVE_TOTILL_REVERSE:
374 switch (vis->last_totill) {
375 case VIS_MOVE_RIGHT_TO:
376 motion = VIS_MOVE_LEFT_TO;
377 break;
378 case VIS_MOVE_LEFT_TO:
379 motion = VIS_MOVE_RIGHT_TO;
380 break;
381 case VIS_MOVE_RIGHT_TILL:
382 motion = VIS_MOVE_LEFT_TILL;
383 break;
384 case VIS_MOVE_LEFT_TILL:
385 motion = VIS_MOVE_RIGHT_TILL;
386 break;
387 default:
388 goto err;
390 break;
391 case VIS_MOVE_MARK:
392 case VIS_MOVE_MARK_LINE:
394 int mark = va_arg(ap, int);
395 if (VIS_MARK_a <= mark && mark < VIS_MARK_INVALID)
396 vis->action.mark = mark;
397 else
398 goto err;
399 break;
401 default:
402 break;
405 if (motion < LENGTH(vis_motions))
406 vis->action.movement = &vis_motions[motion];
407 else
408 vis->action.movement = array_get_ptr(&vis->motions, motion - VIS_MOVE_LAST);
410 if (!vis->action.movement)
411 goto err;
413 va_end(ap);
414 vis_do(vis);
415 return true;
416 err:
417 va_end(ap);
418 return false;
421 const Movement vis_motions[] = {
422 [VIS_MOVE_LINE_UP] = {
423 .cur = view_line_up,
424 .type = LINEWISE|LINEWISE_INCLUSIVE,
426 [VIS_MOVE_LINE_DOWN] = {
427 .cur = view_line_down,
428 .type = LINEWISE|LINEWISE_INCLUSIVE,
430 [VIS_MOVE_SCREEN_LINE_UP] = {
431 .cur = view_screenline_up,
433 [VIS_MOVE_SCREEN_LINE_DOWN] = {
434 .cur = view_screenline_down,
436 [VIS_MOVE_SCREEN_LINE_BEGIN] = {
437 .cur = view_screenline_begin,
438 .type = CHARWISE,
440 [VIS_MOVE_SCREEN_LINE_MIDDLE] = {
441 .cur = view_screenline_middle,
442 .type = CHARWISE,
444 [VIS_MOVE_SCREEN_LINE_END] = {
445 .cur = view_screenline_end,
446 .type = CHARWISE|INCLUSIVE,
448 [VIS_MOVE_LINE_PREV] = {
449 .txt = text_line_prev,
451 [VIS_MOVE_LINE_BEGIN] = {
452 .txt = text_line_begin,
453 .type = IDEMPOTENT,
455 [VIS_MOVE_LINE_START] = {
456 .txt = text_line_start,
457 .type = IDEMPOTENT,
459 [VIS_MOVE_LINE_FINISH] = {
460 .txt = text_line_finish,
461 .type = INCLUSIVE|IDEMPOTENT,
463 [VIS_MOVE_LINE_END] = {
464 .txt = text_line_end,
465 .type = IDEMPOTENT,
467 [VIS_MOVE_LINE_NEXT] = {
468 .txt = text_line_next,
470 [VIS_MOVE_LINE] = {
471 .vis = line,
472 .type = LINEWISE|IDEMPOTENT|JUMP,
474 [VIS_MOVE_COLUMN] = {
475 .vis = column,
476 .type = CHARWISE|IDEMPOTENT,
478 [VIS_MOVE_CHAR_PREV] = {
479 .txt = text_char_prev,
480 .type = CHARWISE,
482 [VIS_MOVE_CHAR_NEXT] = {
483 .txt = text_char_next,
484 .type = CHARWISE,
486 [VIS_MOVE_LINE_CHAR_PREV] = {
487 .txt = text_line_char_prev,
488 .type = CHARWISE,
490 [VIS_MOVE_LINE_CHAR_NEXT] = {
491 .txt = text_line_char_next,
492 .type = CHARWISE,
494 [VIS_MOVE_CODEPOINT_PREV] = {
495 .txt = text_codepoint_prev,
496 .type = CHARWISE,
498 [VIS_MOVE_CODEPOINT_NEXT] = {
499 .txt = text_codepoint_next,
500 .type = CHARWISE,
502 [VIS_MOVE_WORD_NEXT] = {
503 .vis = word_next,
504 .type = CHARWISE|IDEMPOTENT,
506 [VIS_MOVE_WORD_START_PREV] = {
507 .txt = text_word_start_prev,
508 .type = CHARWISE,
510 [VIS_MOVE_WORD_START_NEXT] = {
511 .txt = text_word_start_next,
512 .type = CHARWISE,
514 [VIS_MOVE_WORD_END_PREV] = {
515 .txt = text_word_end_prev,
516 .type = CHARWISE|INCLUSIVE,
518 [VIS_MOVE_WORD_END_NEXT] = {
519 .txt = text_word_end_next,
520 .type = CHARWISE|INCLUSIVE,
522 [VIS_MOVE_LONGWORD_NEXT] = {
523 .vis = longword_next,
524 .type = CHARWISE|IDEMPOTENT,
526 [VIS_MOVE_LONGWORD_START_PREV] = {
527 .txt = text_longword_start_prev,
528 .type = CHARWISE,
530 [VIS_MOVE_LONGWORD_START_NEXT] = {
531 .txt = text_longword_start_next,
532 .type = CHARWISE,
534 [VIS_MOVE_LONGWORD_END_PREV] = {
535 .txt = text_longword_end_prev,
536 .type = CHARWISE|INCLUSIVE,
538 [VIS_MOVE_LONGWORD_END_NEXT] = {
539 .txt = text_longword_end_next,
540 .type = CHARWISE|INCLUSIVE,
542 [VIS_MOVE_SENTENCE_PREV] = {
543 .txt = text_sentence_prev,
544 .type = CHARWISE,
546 [VIS_MOVE_SENTENCE_NEXT] = {
547 .txt = text_sentence_next,
548 .type = CHARWISE,
550 [VIS_MOVE_PARAGRAPH_PREV] = {
551 .txt = text_paragraph_prev,
552 .type = LINEWISE|JUMP,
554 [VIS_MOVE_PARAGRAPH_NEXT] = {
555 .txt = text_paragraph_next,
556 .type = LINEWISE|JUMP,
558 [VIS_MOVE_BLOCK_START] = {
559 .txt = text_block_start,
560 .type = JUMP,
562 [VIS_MOVE_BLOCK_END] = {
563 .txt = text_block_end,
564 .type = JUMP,
566 [VIS_MOVE_PARENTHESE_START] = {
567 .txt = text_parenthese_start,
568 .type = JUMP,
570 [VIS_MOVE_PARENTHESE_END] = {
571 .txt = text_parenthese_end,
572 .type = JUMP,
574 [VIS_MOVE_BRACKET_MATCH] = {
575 .txt = bracket_match,
576 .type = INCLUSIVE|JUMP,
578 [VIS_MOVE_FILE_BEGIN] = {
579 .txt = firstline,
580 .type = LINEWISE|LINEWISE_INCLUSIVE|JUMP|IDEMPOTENT,
582 [VIS_MOVE_FILE_END] = {
583 .txt = lastline,
584 .type = LINEWISE|LINEWISE_INCLUSIVE|JUMP|IDEMPOTENT,
586 [VIS_MOVE_LEFT_TO] = {
587 .vis = to_left,
588 .type = COUNT_EXACT,
590 [VIS_MOVE_RIGHT_TO] = {
591 .vis = to,
592 .type = INCLUSIVE|COUNT_EXACT,
594 [VIS_MOVE_LEFT_TILL] = {
595 .vis = till_left,
596 .type = COUNT_EXACT,
598 [VIS_MOVE_RIGHT_TILL] = {
599 .vis = till,
600 .type = INCLUSIVE|COUNT_EXACT,
602 [VIS_MOVE_MARK] = {
603 .file = mark_goto,
604 .type = JUMP|IDEMPOTENT,
606 [VIS_MOVE_MARK_LINE] = {
607 .file = mark_line_goto,
608 .type = LINEWISE|JUMP|IDEMPOTENT,
610 [VIS_MOVE_SEARCH_WORD_FORWARD] = {
611 .vis = search_word_forward,
612 .type = JUMP,
614 [VIS_MOVE_SEARCH_WORD_BACKWARD] = {
615 .vis = search_word_backward,
616 .type = JUMP,
618 [VIS_MOVE_SEARCH_REPEAT_FORWARD] = {
619 .vis = search_forward,
620 .type = JUMP,
622 [VIS_MOVE_SEARCH_REPEAT_BACKWARD] = {
623 .vis = search_backward,
624 .type = JUMP,
626 [VIS_MOVE_WINDOW_LINE_TOP] = {
627 .view = view_lines_top,
628 .type = LINEWISE|JUMP|IDEMPOTENT,
630 [VIS_MOVE_WINDOW_LINE_MIDDLE] = {
631 .view = view_lines_middle,
632 .type = LINEWISE|JUMP|IDEMPOTENT,
634 [VIS_MOVE_WINDOW_LINE_BOTTOM] = {
635 .view = view_lines_bottom,
636 .type = LINEWISE|JUMP|IDEMPOTENT,
638 [VIS_MOVE_CHANGELIST_NEXT] = {
639 .win = window_changelist_next,
640 .type = INCLUSIVE,
642 [VIS_MOVE_CHANGELIST_PREV] = {
643 .win = window_changelist_prev,
644 .type = INCLUSIVE,
646 [VIS_MOVE_JUMPLIST_NEXT] = {
647 .win = window_jumplist_next,
648 .type = INCLUSIVE,
650 [VIS_MOVE_JUMPLIST_PREV] = {
651 .win = window_jumplist_prev,
652 .type = INCLUSIVE,
654 [VIS_MOVE_NOP] = {
655 .win = window_nop,
656 .type = IDEMPOTENT,
658 [VIS_MOVE_PERCENT] = {
659 .vis = percent,
660 .type = IDEMPOTENT,
662 [VIS_MOVE_BYTE] = {
663 .vis = byte,
664 .type = IDEMPOTENT,
666 [VIS_MOVE_BYTE_LEFT] = {
667 .vis = byte_left,
668 .type = IDEMPOTENT,
670 [VIS_MOVE_BYTE_RIGHT] = {
671 .vis = byte_right,
672 .type = IDEMPOTENT,