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, ¤t)) {
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 };