vis: implement normal/outer paragraph text object
[vis.git] / vis-motions.c
blobb72f2806805cb7c90a3f508984dfbe35cdfb46a9
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(Vis *vis, Text *txt, size_t pos) {
116 char c;
117 if (pos == text_line_end(txt, pos))
118 return pos;
119 size_t hit = text_line_find_next(txt, pos+1, vis->search_char);
120 if (!text_byte_get(txt, hit, &c) || c != vis->search_char[0])
121 return pos;
122 return hit;
125 static size_t till(Vis *vis, Text *txt, size_t pos) {
126 size_t hit = to(vis, txt, pos+1);
127 if (pos == text_line_end(txt, pos))
128 return pos;
129 if (hit != pos)
130 return text_char_prev(txt, hit);
131 return pos;
134 static size_t to_left(Vis *vis, Text *txt, size_t pos) {
135 return text_line_find_prev(txt, pos, vis->search_char);
138 static size_t till_left(Vis *vis, Text *txt, size_t pos) {
139 if (pos == text_line_begin(txt, pos))
140 return pos;
141 size_t hit = to_left(vis, txt, pos-1);
142 if (hit != pos-1)
143 return text_char_next(txt, hit);
144 return pos;
147 static size_t firstline(Text *txt, size_t pos) {
148 return text_line_start(txt, 0);
151 static size_t line(Vis *vis, Text *txt, size_t pos) {
152 int count = vis_count_get_default(vis, 1);
153 return text_line_start(txt, text_pos_by_lineno(txt, count));
156 static size_t lastline(Text *txt, size_t pos) {
157 pos = text_size(txt);
158 return text_line_start(txt, pos > 0 ? pos-1 : pos);
161 static size_t column(Vis *vis, Text *txt, size_t pos) {
162 return text_line_offset(txt, pos, vis_count_get_default(vis, 0));
165 static size_t view_lines_top(Vis *vis, View *view) {
166 return view_screenline_goto(view, vis_count_get_default(vis, 1));
169 static size_t view_lines_middle(Vis *vis, View *view) {
170 int h = view_height_get(view);
171 return view_screenline_goto(view, h/2);
174 static size_t view_lines_bottom(Vis *vis, View *view) {
175 int h = view_height_get(vis->win->view);
176 return view_screenline_goto(vis->win->view, h - vis_count_get_default(vis, 0));
179 static size_t window_nop(Vis *vis, Win *win, size_t pos) {
180 return pos;
183 static size_t bracket_match(Text *txt, size_t pos) {
184 size_t hit = text_bracket_match_symbol(txt, pos, "(){}[]<>");
185 if (hit != pos)
186 return hit;
187 char current;
188 Iterator it = text_iterator_get(txt, pos);
189 while (text_iterator_byte_get(&it, &current)) {
190 switch (current) {
191 case '(':
192 case ')':
193 case '{':
194 case '}':
195 case '[':
196 case ']':
197 case '<':
198 case '>':
199 return it.pos;
201 text_iterator_byte_next(&it, NULL);
203 return pos;
206 static size_t percent(Vis *vis, Text *txt, size_t pos) {
207 int ratio = vis_count_get_default(vis, 0);
208 if (ratio > 100)
209 ratio = 100;
210 return text_size(txt) * ratio / 100;
213 static size_t byte(Vis *vis, Text *txt, size_t pos) {
214 pos = vis_count_get_default(vis, 0);
215 size_t max = text_size(txt);
216 return pos <= max ? pos : max;
219 static size_t byte_left(Vis *vis, Text *txt, size_t pos) {
220 size_t off = vis_count_get_default(vis, 1);
221 return off <= pos ? pos-off : 0;
224 static size_t byte_right(Vis *vis, Text *txt, size_t pos) {
225 size_t off = vis_count_get_default(vis, 1);
226 size_t new = pos + off;
227 size_t max = text_size(txt);
228 return new <= max && new > pos ? new : max;
231 void vis_motion_type(Vis *vis, enum VisMotionType type) {
232 vis->action.type = type;
235 int vis_motion_register(Vis *vis, enum VisMotionType type, void *data, VisMotionFunction *motion) {
237 Movement *move = calloc(1, sizeof *move);
238 if (!move)
239 return -1;
241 move->user = motion;
242 move->type = type;
243 move->data = data;
245 if (array_add_ptr(&vis->motions, move))
246 return VIS_MOVE_LAST + array_length(&vis->motions) - 1;
247 free(move);
248 return -1;
251 bool vis_motion(Vis *vis, enum VisMotion motion, ...) {
252 va_list ap;
253 va_start(ap, motion);
255 switch (motion) {
256 case VIS_MOVE_WORD_START_NEXT:
257 if (vis->action.op == &vis_operators[VIS_OP_CHANGE])
258 motion = VIS_MOVE_WORD_NEXT;
259 break;
260 case VIS_MOVE_LONGWORD_START_NEXT:
261 if (vis->action.op == &vis_operators[VIS_OP_CHANGE])
262 motion = VIS_MOVE_LONGWORD_NEXT;
263 break;
264 case VIS_MOVE_SEARCH_FORWARD:
265 case VIS_MOVE_SEARCH_BACKWARD:
267 const char *pattern = va_arg(ap, char*);
268 Regex *regex = vis_regex(vis, pattern);
269 if (!regex) {
270 vis_cancel(vis);
271 goto err;
273 text_regex_free(regex);
274 if (motion == VIS_MOVE_SEARCH_FORWARD)
275 motion = VIS_MOVE_SEARCH_REPEAT_FORWARD;
276 else
277 motion = VIS_MOVE_SEARCH_REPEAT_BACKWARD;
278 vis->search_direction = motion;
279 break;
281 case VIS_MOVE_SEARCH_REPEAT:
282 case VIS_MOVE_SEARCH_REPEAT_REVERSE:
284 if (!vis->search_direction)
285 vis->search_direction = VIS_MOVE_SEARCH_REPEAT_FORWARD;
286 if (motion == VIS_MOVE_SEARCH_REPEAT) {
287 motion = vis->search_direction;
288 } else {
289 motion = vis->search_direction == VIS_MOVE_SEARCH_REPEAT_FORWARD ?
290 VIS_MOVE_SEARCH_REPEAT_BACKWARD :
291 VIS_MOVE_SEARCH_REPEAT_FORWARD;
293 break;
295 case VIS_MOVE_RIGHT_TO:
296 case VIS_MOVE_LEFT_TO:
297 case VIS_MOVE_RIGHT_TILL:
298 case VIS_MOVE_LEFT_TILL:
300 const char *key = va_arg(ap, char*);
301 if (!key)
302 goto err;
303 strncpy(vis->search_char, key, sizeof(vis->search_char));
304 vis->search_char[sizeof(vis->search_char)-1] = '\0';
305 vis->last_totill = motion;
306 break;
308 case VIS_MOVE_TOTILL_REPEAT:
309 if (!vis->last_totill)
310 goto err;
311 motion = vis->last_totill;
312 break;
313 case VIS_MOVE_TOTILL_REVERSE:
314 switch (vis->last_totill) {
315 case VIS_MOVE_RIGHT_TO:
316 motion = VIS_MOVE_LEFT_TO;
317 break;
318 case VIS_MOVE_LEFT_TO:
319 motion = VIS_MOVE_RIGHT_TO;
320 break;
321 case VIS_MOVE_RIGHT_TILL:
322 motion = VIS_MOVE_LEFT_TILL;
323 break;
324 case VIS_MOVE_LEFT_TILL:
325 motion = VIS_MOVE_RIGHT_TILL;
326 break;
327 default:
328 goto err;
330 break;
331 default:
332 break;
335 if (motion < LENGTH(vis_motions))
336 vis->action.movement = &vis_motions[motion];
337 else
338 vis->action.movement = array_get_ptr(&vis->motions, motion - VIS_MOVE_LAST);
340 if (!vis->action.movement)
341 goto err;
343 va_end(ap);
344 vis_do(vis);
345 return true;
346 err:
347 va_end(ap);
348 return false;
351 const Movement vis_motions[] = {
352 [VIS_MOVE_LINE_UP] = {
353 .cur = view_line_up,
354 .type = LINEWISE|LINEWISE_INCLUSIVE,
356 [VIS_MOVE_LINE_DOWN] = {
357 .cur = view_line_down,
358 .type = LINEWISE|LINEWISE_INCLUSIVE,
360 [VIS_MOVE_SCREEN_LINE_UP] = {
361 .cur = view_screenline_up,
363 [VIS_MOVE_SCREEN_LINE_DOWN] = {
364 .cur = view_screenline_down,
366 [VIS_MOVE_SCREEN_LINE_BEGIN] = {
367 .cur = view_screenline_begin,
368 .type = CHARWISE,
370 [VIS_MOVE_SCREEN_LINE_MIDDLE] = {
371 .cur = view_screenline_middle,
372 .type = CHARWISE,
374 [VIS_MOVE_SCREEN_LINE_END] = {
375 .cur = view_screenline_end,
376 .type = CHARWISE|INCLUSIVE,
378 [VIS_MOVE_LINE_PREV] = {
379 .txt = text_line_prev,
381 [VIS_MOVE_LINE_BEGIN] = {
382 .txt = text_line_begin,
383 .type = IDEMPOTENT,
385 [VIS_MOVE_LINE_START] = {
386 .txt = text_line_start,
387 .type = IDEMPOTENT,
389 [VIS_MOVE_LINE_FINISH] = {
390 .txt = text_line_finish,
391 .type = INCLUSIVE|IDEMPOTENT,
393 [VIS_MOVE_LINE_END] = {
394 .txt = text_line_end,
395 .type = IDEMPOTENT,
397 [VIS_MOVE_LINE_NEXT] = {
398 .txt = text_line_next,
400 [VIS_MOVE_LINE] = {
401 .vis = line,
402 .type = LINEWISE|IDEMPOTENT|JUMP,
404 [VIS_MOVE_COLUMN] = {
405 .vis = column,
406 .type = CHARWISE|IDEMPOTENT,
408 [VIS_MOVE_CHAR_PREV] = {
409 .txt = text_char_prev,
410 .type = CHARWISE,
412 [VIS_MOVE_CHAR_NEXT] = {
413 .txt = text_char_next,
414 .type = CHARWISE,
416 [VIS_MOVE_LINE_CHAR_PREV] = {
417 .txt = text_line_char_prev,
418 .type = CHARWISE,
420 [VIS_MOVE_LINE_CHAR_NEXT] = {
421 .txt = text_line_char_next,
422 .type = CHARWISE,
424 [VIS_MOVE_CODEPOINT_PREV] = {
425 .txt = text_codepoint_prev,
426 .type = CHARWISE,
428 [VIS_MOVE_CODEPOINT_NEXT] = {
429 .txt = text_codepoint_next,
430 .type = CHARWISE,
432 [VIS_MOVE_WORD_NEXT] = {
433 .vis = word_next,
434 .type = CHARWISE|IDEMPOTENT,
436 [VIS_MOVE_WORD_START_PREV] = {
437 .txt = text_word_start_prev,
438 .type = CHARWISE,
440 [VIS_MOVE_WORD_START_NEXT] = {
441 .txt = text_word_start_next,
442 .type = CHARWISE,
444 [VIS_MOVE_WORD_END_PREV] = {
445 .txt = text_word_end_prev,
446 .type = CHARWISE|INCLUSIVE,
448 [VIS_MOVE_WORD_END_NEXT] = {
449 .txt = text_word_end_next,
450 .type = CHARWISE|INCLUSIVE,
452 [VIS_MOVE_LONGWORD_NEXT] = {
453 .vis = longword_next,
454 .type = CHARWISE|IDEMPOTENT,
456 [VIS_MOVE_LONGWORD_START_PREV] = {
457 .txt = text_longword_start_prev,
458 .type = CHARWISE,
460 [VIS_MOVE_LONGWORD_START_NEXT] = {
461 .txt = text_longword_start_next,
462 .type = CHARWISE,
464 [VIS_MOVE_LONGWORD_END_PREV] = {
465 .txt = text_longword_end_prev,
466 .type = CHARWISE|INCLUSIVE,
468 [VIS_MOVE_LONGWORD_END_NEXT] = {
469 .txt = text_longword_end_next,
470 .type = CHARWISE|INCLUSIVE,
472 [VIS_MOVE_SENTENCE_PREV] = {
473 .txt = text_sentence_prev,
474 .type = CHARWISE,
476 [VIS_MOVE_SENTENCE_NEXT] = {
477 .txt = text_sentence_next,
478 .type = CHARWISE,
480 [VIS_MOVE_PARAGRAPH_PREV] = {
481 .txt = text_paragraph_prev,
482 .type = LINEWISE|JUMP,
484 [VIS_MOVE_PARAGRAPH_NEXT] = {
485 .txt = text_paragraph_next,
486 .type = LINEWISE|JUMP,
488 [VIS_MOVE_BLOCK_START] = {
489 .txt = text_block_start,
490 .type = JUMP,
492 [VIS_MOVE_BLOCK_END] = {
493 .txt = text_block_end,
494 .type = JUMP,
496 [VIS_MOVE_PARENTHESE_START] = {
497 .txt = text_parenthese_start,
498 .type = JUMP,
500 [VIS_MOVE_PARENTHESE_END] = {
501 .txt = text_parenthese_end,
502 .type = JUMP,
504 [VIS_MOVE_BRACKET_MATCH] = {
505 .txt = bracket_match,
506 .type = INCLUSIVE|JUMP,
508 [VIS_MOVE_FILE_BEGIN] = {
509 .txt = firstline,
510 .type = LINEWISE|LINEWISE_INCLUSIVE|JUMP|IDEMPOTENT,
512 [VIS_MOVE_FILE_END] = {
513 .txt = lastline,
514 .type = LINEWISE|LINEWISE_INCLUSIVE|JUMP|IDEMPOTENT,
516 [VIS_MOVE_LEFT_TO] = {
517 .vis = to_left,
518 .type = COUNT_EXACT,
520 [VIS_MOVE_RIGHT_TO] = {
521 .vis = to,
522 .type = INCLUSIVE|COUNT_EXACT,
524 [VIS_MOVE_LEFT_TILL] = {
525 .vis = till_left,
526 .type = COUNT_EXACT,
528 [VIS_MOVE_RIGHT_TILL] = {
529 .vis = till,
530 .type = INCLUSIVE|COUNT_EXACT,
532 [VIS_MOVE_SEARCH_WORD_FORWARD] = {
533 .vis = search_word_forward,
534 .type = JUMP,
536 [VIS_MOVE_SEARCH_WORD_BACKWARD] = {
537 .vis = search_word_backward,
538 .type = JUMP,
540 [VIS_MOVE_SEARCH_REPEAT_FORWARD] = {
541 .vis = search_forward,
542 .type = JUMP,
544 [VIS_MOVE_SEARCH_REPEAT_BACKWARD] = {
545 .vis = search_backward,
546 .type = JUMP,
548 [VIS_MOVE_WINDOW_LINE_TOP] = {
549 .view = view_lines_top,
550 .type = LINEWISE|JUMP|IDEMPOTENT,
552 [VIS_MOVE_WINDOW_LINE_MIDDLE] = {
553 .view = view_lines_middle,
554 .type = LINEWISE|JUMP|IDEMPOTENT,
556 [VIS_MOVE_WINDOW_LINE_BOTTOM] = {
557 .view = view_lines_bottom,
558 .type = LINEWISE|JUMP|IDEMPOTENT,
560 [VIS_MOVE_NOP] = {
561 .win = window_nop,
562 .type = IDEMPOTENT,
564 [VIS_MOVE_PERCENT] = {
565 .vis = percent,
566 .type = IDEMPOTENT,
568 [VIS_MOVE_BYTE] = {
569 .vis = byte,
570 .type = IDEMPOTENT,
572 [VIS_MOVE_BYTE_LEFT] = {
573 .vis = byte_left,
574 .type = IDEMPOTENT,
576 [VIS_MOVE_BYTE_RIGHT] = {
577 .vis = byte_right,
578 .type = IDEMPOTENT,