view: introduce view_selections_normalize
[vis.git] / vis-motions.c
blob9917ce5866fbb827976fb33c2b67eef20b765883
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 if (vis->interrupted)
89 return pos;
90 size_t newpos = motion->txt(txt, pos);
91 if (newpos == pos)
92 break;
93 pos = newpos;
96 if (motion->type & INCLUSIVE)
97 pos = text_char_next(txt, pos);
99 return pos;
102 static size_t word_next(Vis *vis, Text *txt, size_t pos) {
103 return common_word_next(vis, txt, pos, VIS_MOVE_WORD_END_NEXT);
106 static size_t longword_next(Vis *vis, Text *txt, size_t pos) {
107 return common_word_next(vis, txt, pos, VIS_MOVE_LONGWORD_END_NEXT);
110 static size_t mark_goto(Vis *vis, File *file, size_t pos) {
111 return text_mark_get(file->text, file->marks[vis->action.mark]);
114 static size_t mark_line_goto(Vis *vis, File *file, size_t pos) {
115 return text_line_start(file->text, mark_goto(vis, file, pos));
118 static size_t to(Vis *vis, Text *txt, size_t pos) {
119 char c;
120 if (pos == text_line_end(txt, pos))
121 return pos;
122 size_t hit = text_line_find_next(txt, pos+1, vis->search_char);
123 if (!text_byte_get(txt, hit, &c) || c != vis->search_char[0])
124 return pos;
125 return hit;
128 static size_t till(Vis *vis, Text *txt, size_t pos) {
129 size_t hit = to(vis, txt, pos+1);
130 if (pos == text_line_end(txt, pos))
131 return pos;
132 if (hit != pos)
133 return text_char_prev(txt, hit);
134 return pos;
137 static size_t to_left(Vis *vis, Text *txt, size_t pos) {
138 return text_line_find_prev(txt, pos, vis->search_char);
141 static size_t till_left(Vis *vis, Text *txt, size_t pos) {
142 if (pos == text_line_begin(txt, pos))
143 return pos;
144 size_t hit = to_left(vis, txt, pos-1);
145 if (hit != pos-1)
146 return text_char_next(txt, hit);
147 return pos;
150 static size_t firstline(Text *txt, size_t pos) {
151 return text_line_start(txt, 0);
154 static size_t line(Vis *vis, Text *txt, size_t pos) {
155 int count = vis_count_get_default(vis, 1);
156 return text_line_start(txt, text_pos_by_lineno(txt, count));
159 static size_t lastline(Text *txt, size_t pos) {
160 pos = text_size(txt);
161 return text_line_start(txt, pos > 0 ? pos-1 : pos);
164 static size_t column(Vis *vis, Text *txt, size_t pos) {
165 return text_line_offset(txt, pos, vis_count_get_default(vis, 0));
168 static size_t view_lines_top(Vis *vis, View *view) {
169 return view_screenline_goto(view, vis_count_get_default(vis, 1));
172 static size_t view_lines_middle(Vis *vis, View *view) {
173 int h = view_height_get(view);
174 return view_screenline_goto(view, h/2);
177 static size_t view_lines_bottom(Vis *vis, View *view) {
178 int h = view_height_get(vis->win->view);
179 return view_screenline_goto(vis->win->view, h - vis_count_get_default(vis, 0));
182 static size_t window_changelist_next(Vis *vis, Win *win, size_t pos) {
183 ChangeList *cl = &win->changelist;
184 Text *txt = win->file->text;
185 time_t state = text_state(txt);
186 if (cl->state != state)
187 cl->index = 0;
188 else if (cl->index > 0 && pos == cl->pos)
189 cl->index--;
190 size_t newpos = pos;
191 if (newpos == EPOS)
192 cl->index++;
193 else
194 cl->pos = newpos;
195 cl->state = state;
196 return cl->pos;
199 static size_t window_changelist_prev(Vis *vis, Win *win, size_t pos) {
200 ChangeList *cl = &win->changelist;
201 Text *txt = win->file->text;
202 time_t state = text_state(txt);
203 if (cl->state != state)
204 cl->index = 0;
205 else if (pos == cl->pos)
206 win->changelist.index++;
207 size_t newpos = pos;
208 if (newpos == EPOS)
209 cl->index--;
210 else
211 cl->pos = newpos;
212 cl->state = state;
213 return cl->pos;
216 static size_t window_jumplist_next(Vis *vis, Win *win, size_t cur) {
217 while (win->jumplist) {
218 Mark mark = (Mark)ringbuf_next(win->jumplist);
219 if (!mark)
220 return cur;
221 size_t pos = text_mark_get(win->file->text, mark);
222 if (pos != EPOS && pos != cur)
223 return pos;
225 return cur;
228 static size_t window_jumplist_prev(Vis *vis, Win *win, size_t cur) {
229 while (win->jumplist) {
230 Mark mark = (Mark)ringbuf_prev(win->jumplist);
231 if (!mark)
232 return cur;
233 size_t pos = text_mark_get(win->file->text, mark);
234 if (pos != EPOS && pos != cur)
235 return pos;
237 return cur;
240 static size_t window_nop(Vis *vis, Win *win, size_t pos) {
241 return pos;
244 static size_t bracket_match(Text *txt, size_t pos) {
245 size_t hit = text_bracket_match_symbol(txt, pos, "(){}[]<>");
246 if (hit != pos)
247 return hit;
248 char current;
249 Iterator it = text_iterator_get(txt, pos);
250 while (text_iterator_byte_get(&it, &current)) {
251 switch (current) {
252 case '(':
253 case ')':
254 case '{':
255 case '}':
256 case '[':
257 case ']':
258 case '<':
259 case '>':
260 return it.pos;
262 text_iterator_byte_next(&it, NULL);
264 return pos;
267 static size_t percent(Vis *vis, Text *txt, size_t pos) {
268 int ratio = vis_count_get_default(vis, 0);
269 if (ratio > 100)
270 ratio = 100;
271 return text_size(txt) * ratio / 100;
274 static size_t byte(Vis *vis, Text *txt, size_t pos) {
275 pos = vis_count_get_default(vis, 0);
276 size_t max = text_size(txt);
277 return pos <= max ? pos : max;
280 static size_t byte_left(Vis *vis, Text *txt, size_t pos) {
281 size_t off = vis_count_get_default(vis, 1);
282 return off <= pos ? pos-off : 0;
285 static size_t byte_right(Vis *vis, Text *txt, size_t pos) {
286 size_t off = vis_count_get_default(vis, 1);
287 size_t new = pos + off;
288 size_t max = text_size(txt);
289 return new <= max && new > pos ? new : max;
292 void vis_motion_type(Vis *vis, enum VisMotionType type) {
293 vis->action.type = type;
296 int vis_motion_register(Vis *vis, enum VisMotionType type, void *data, VisMotionFunction *motion) {
298 Movement *move = calloc(1, sizeof *move);
299 if (!move)
300 return -1;
302 move->user = motion;
303 move->type = type;
304 move->data = data;
306 if (array_add_ptr(&vis->motions, move))
307 return VIS_MOVE_LAST + array_length(&vis->motions) - 1;
308 free(move);
309 return -1;
312 bool vis_motion(Vis *vis, enum VisMotion motion, ...) {
313 va_list ap;
314 va_start(ap, motion);
316 switch (motion) {
317 case VIS_MOVE_WORD_START_NEXT:
318 if (vis->action.op == &vis_operators[VIS_OP_CHANGE])
319 motion = VIS_MOVE_WORD_NEXT;
320 break;
321 case VIS_MOVE_LONGWORD_START_NEXT:
322 if (vis->action.op == &vis_operators[VIS_OP_CHANGE])
323 motion = VIS_MOVE_LONGWORD_NEXT;
324 break;
325 case VIS_MOVE_SEARCH_FORWARD:
326 case VIS_MOVE_SEARCH_BACKWARD:
328 const char *pattern = va_arg(ap, char*);
329 Regex *regex = vis_regex(vis, pattern);
330 if (!regex) {
331 vis_cancel(vis);
332 goto err;
334 text_regex_free(regex);
335 if (motion == VIS_MOVE_SEARCH_FORWARD)
336 motion = VIS_MOVE_SEARCH_REPEAT_FORWARD;
337 else
338 motion = VIS_MOVE_SEARCH_REPEAT_BACKWARD;
339 vis->search_direction = motion;
340 break;
342 case VIS_MOVE_SEARCH_REPEAT:
343 case VIS_MOVE_SEARCH_REPEAT_REVERSE:
345 if (!vis->search_direction)
346 vis->search_direction = VIS_MOVE_SEARCH_REPEAT_FORWARD;
347 if (motion == VIS_MOVE_SEARCH_REPEAT) {
348 motion = vis->search_direction;
349 } else {
350 motion = vis->search_direction == VIS_MOVE_SEARCH_REPEAT_FORWARD ?
351 VIS_MOVE_SEARCH_REPEAT_BACKWARD :
352 VIS_MOVE_SEARCH_REPEAT_FORWARD;
354 break;
356 case VIS_MOVE_RIGHT_TO:
357 case VIS_MOVE_LEFT_TO:
358 case VIS_MOVE_RIGHT_TILL:
359 case VIS_MOVE_LEFT_TILL:
361 const char *key = va_arg(ap, char*);
362 if (!key)
363 goto err;
364 strncpy(vis->search_char, key, sizeof(vis->search_char));
365 vis->search_char[sizeof(vis->search_char)-1] = '\0';
366 vis->last_totill = motion;
367 break;
369 case VIS_MOVE_TOTILL_REPEAT:
370 if (!vis->last_totill)
371 goto err;
372 motion = vis->last_totill;
373 break;
374 case VIS_MOVE_TOTILL_REVERSE:
375 switch (vis->last_totill) {
376 case VIS_MOVE_RIGHT_TO:
377 motion = VIS_MOVE_LEFT_TO;
378 break;
379 case VIS_MOVE_LEFT_TO:
380 motion = VIS_MOVE_RIGHT_TO;
381 break;
382 case VIS_MOVE_RIGHT_TILL:
383 motion = VIS_MOVE_LEFT_TILL;
384 break;
385 case VIS_MOVE_LEFT_TILL:
386 motion = VIS_MOVE_RIGHT_TILL;
387 break;
388 default:
389 goto err;
391 break;
392 case VIS_MOVE_MARK:
393 case VIS_MOVE_MARK_LINE:
395 int mark = va_arg(ap, int);
396 if (VIS_MARK_a <= mark && mark < VIS_MARK_INVALID)
397 vis->action.mark = mark;
398 else
399 goto err;
400 break;
402 default:
403 break;
406 if (motion < LENGTH(vis_motions))
407 vis->action.movement = &vis_motions[motion];
408 else
409 vis->action.movement = array_get_ptr(&vis->motions, motion - VIS_MOVE_LAST);
411 if (!vis->action.movement)
412 goto err;
414 va_end(ap);
415 vis_do(vis);
416 return true;
417 err:
418 va_end(ap);
419 return false;
422 const Movement vis_motions[] = {
423 [VIS_MOVE_LINE_UP] = {
424 .cur = view_line_up,
425 .type = LINEWISE|LINEWISE_INCLUSIVE,
427 [VIS_MOVE_LINE_DOWN] = {
428 .cur = view_line_down,
429 .type = LINEWISE|LINEWISE_INCLUSIVE,
431 [VIS_MOVE_SCREEN_LINE_UP] = {
432 .cur = view_screenline_up,
434 [VIS_MOVE_SCREEN_LINE_DOWN] = {
435 .cur = view_screenline_down,
437 [VIS_MOVE_SCREEN_LINE_BEGIN] = {
438 .cur = view_screenline_begin,
439 .type = CHARWISE,
441 [VIS_MOVE_SCREEN_LINE_MIDDLE] = {
442 .cur = view_screenline_middle,
443 .type = CHARWISE,
445 [VIS_MOVE_SCREEN_LINE_END] = {
446 .cur = view_screenline_end,
447 .type = CHARWISE|INCLUSIVE,
449 [VIS_MOVE_LINE_PREV] = {
450 .txt = text_line_prev,
452 [VIS_MOVE_LINE_BEGIN] = {
453 .txt = text_line_begin,
454 .type = IDEMPOTENT,
456 [VIS_MOVE_LINE_START] = {
457 .txt = text_line_start,
458 .type = IDEMPOTENT,
460 [VIS_MOVE_LINE_FINISH] = {
461 .txt = text_line_finish,
462 .type = INCLUSIVE|IDEMPOTENT,
464 [VIS_MOVE_LINE_END] = {
465 .txt = text_line_end,
466 .type = IDEMPOTENT,
468 [VIS_MOVE_LINE_NEXT] = {
469 .txt = text_line_next,
471 [VIS_MOVE_LINE] = {
472 .vis = line,
473 .type = LINEWISE|IDEMPOTENT|JUMP,
475 [VIS_MOVE_COLUMN] = {
476 .vis = column,
477 .type = CHARWISE|IDEMPOTENT,
479 [VIS_MOVE_CHAR_PREV] = {
480 .txt = text_char_prev,
481 .type = CHARWISE,
483 [VIS_MOVE_CHAR_NEXT] = {
484 .txt = text_char_next,
485 .type = CHARWISE,
487 [VIS_MOVE_LINE_CHAR_PREV] = {
488 .txt = text_line_char_prev,
489 .type = CHARWISE,
491 [VIS_MOVE_LINE_CHAR_NEXT] = {
492 .txt = text_line_char_next,
493 .type = CHARWISE,
495 [VIS_MOVE_CODEPOINT_PREV] = {
496 .txt = text_codepoint_prev,
497 .type = CHARWISE,
499 [VIS_MOVE_CODEPOINT_NEXT] = {
500 .txt = text_codepoint_next,
501 .type = CHARWISE,
503 [VIS_MOVE_WORD_NEXT] = {
504 .vis = word_next,
505 .type = CHARWISE|IDEMPOTENT,
507 [VIS_MOVE_WORD_START_PREV] = {
508 .txt = text_word_start_prev,
509 .type = CHARWISE,
511 [VIS_MOVE_WORD_START_NEXT] = {
512 .txt = text_word_start_next,
513 .type = CHARWISE,
515 [VIS_MOVE_WORD_END_PREV] = {
516 .txt = text_word_end_prev,
517 .type = CHARWISE|INCLUSIVE,
519 [VIS_MOVE_WORD_END_NEXT] = {
520 .txt = text_word_end_next,
521 .type = CHARWISE|INCLUSIVE,
523 [VIS_MOVE_LONGWORD_NEXT] = {
524 .vis = longword_next,
525 .type = CHARWISE|IDEMPOTENT,
527 [VIS_MOVE_LONGWORD_START_PREV] = {
528 .txt = text_longword_start_prev,
529 .type = CHARWISE,
531 [VIS_MOVE_LONGWORD_START_NEXT] = {
532 .txt = text_longword_start_next,
533 .type = CHARWISE,
535 [VIS_MOVE_LONGWORD_END_PREV] = {
536 .txt = text_longword_end_prev,
537 .type = CHARWISE|INCLUSIVE,
539 [VIS_MOVE_LONGWORD_END_NEXT] = {
540 .txt = text_longword_end_next,
541 .type = CHARWISE|INCLUSIVE,
543 [VIS_MOVE_SENTENCE_PREV] = {
544 .txt = text_sentence_prev,
545 .type = CHARWISE,
547 [VIS_MOVE_SENTENCE_NEXT] = {
548 .txt = text_sentence_next,
549 .type = CHARWISE,
551 [VIS_MOVE_PARAGRAPH_PREV] = {
552 .txt = text_paragraph_prev,
553 .type = LINEWISE|JUMP,
555 [VIS_MOVE_PARAGRAPH_NEXT] = {
556 .txt = text_paragraph_next,
557 .type = LINEWISE|JUMP,
559 [VIS_MOVE_BLOCK_START] = {
560 .txt = text_block_start,
561 .type = JUMP,
563 [VIS_MOVE_BLOCK_END] = {
564 .txt = text_block_end,
565 .type = JUMP,
567 [VIS_MOVE_PARENTHESE_START] = {
568 .txt = text_parenthese_start,
569 .type = JUMP,
571 [VIS_MOVE_PARENTHESE_END] = {
572 .txt = text_parenthese_end,
573 .type = JUMP,
575 [VIS_MOVE_BRACKET_MATCH] = {
576 .txt = bracket_match,
577 .type = INCLUSIVE|JUMP,
579 [VIS_MOVE_FILE_BEGIN] = {
580 .txt = firstline,
581 .type = LINEWISE|LINEWISE_INCLUSIVE|JUMP|IDEMPOTENT,
583 [VIS_MOVE_FILE_END] = {
584 .txt = lastline,
585 .type = LINEWISE|LINEWISE_INCLUSIVE|JUMP|IDEMPOTENT,
587 [VIS_MOVE_LEFT_TO] = {
588 .vis = to_left,
589 .type = COUNT_EXACT,
591 [VIS_MOVE_RIGHT_TO] = {
592 .vis = to,
593 .type = INCLUSIVE|COUNT_EXACT,
595 [VIS_MOVE_LEFT_TILL] = {
596 .vis = till_left,
597 .type = COUNT_EXACT,
599 [VIS_MOVE_RIGHT_TILL] = {
600 .vis = till,
601 .type = INCLUSIVE|COUNT_EXACT,
603 [VIS_MOVE_MARK] = {
604 .file = mark_goto,
605 .type = JUMP|IDEMPOTENT,
607 [VIS_MOVE_MARK_LINE] = {
608 .file = mark_line_goto,
609 .type = LINEWISE|JUMP|IDEMPOTENT,
611 [VIS_MOVE_SEARCH_WORD_FORWARD] = {
612 .vis = search_word_forward,
613 .type = JUMP,
615 [VIS_MOVE_SEARCH_WORD_BACKWARD] = {
616 .vis = search_word_backward,
617 .type = JUMP,
619 [VIS_MOVE_SEARCH_REPEAT_FORWARD] = {
620 .vis = search_forward,
621 .type = JUMP,
623 [VIS_MOVE_SEARCH_REPEAT_BACKWARD] = {
624 .vis = search_backward,
625 .type = JUMP,
627 [VIS_MOVE_WINDOW_LINE_TOP] = {
628 .view = view_lines_top,
629 .type = LINEWISE|JUMP|IDEMPOTENT,
631 [VIS_MOVE_WINDOW_LINE_MIDDLE] = {
632 .view = view_lines_middle,
633 .type = LINEWISE|JUMP|IDEMPOTENT,
635 [VIS_MOVE_WINDOW_LINE_BOTTOM] = {
636 .view = view_lines_bottom,
637 .type = LINEWISE|JUMP|IDEMPOTENT,
639 [VIS_MOVE_CHANGELIST_NEXT] = {
640 .win = window_changelist_next,
641 .type = INCLUSIVE,
643 [VIS_MOVE_CHANGELIST_PREV] = {
644 .win = window_changelist_prev,
645 .type = INCLUSIVE,
647 [VIS_MOVE_JUMPLIST_NEXT] = {
648 .win = window_jumplist_next,
649 .type = INCLUSIVE,
651 [VIS_MOVE_JUMPLIST_PREV] = {
652 .win = window_jumplist_prev,
653 .type = INCLUSIVE,
655 [VIS_MOVE_NOP] = {
656 .win = window_nop,
657 .type = IDEMPOTENT,
659 [VIS_MOVE_PERCENT] = {
660 .vis = percent,
661 .type = IDEMPOTENT,
663 [VIS_MOVE_BYTE] = {
664 .vis = byte,
665 .type = IDEMPOTENT,
667 [VIS_MOVE_BYTE_LEFT] = {
668 .vis = byte_left,
669 .type = IDEMPOTENT,
671 [VIS_MOVE_BYTE_RIGHT] = {
672 .vis = byte_right,
673 .type = IDEMPOTENT,