text: improve text_line_down on the last line of the file
[vis.git] / vis-motions.c
blob3702efa10e4cd8bcc340c5b48a4b2a5b6e8faf3b
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, "(){}[]<>'\"`", NULL);
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 case '"':
200 case '\'':
201 case '`':
202 return it.pos;
204 text_iterator_byte_next(&it, NULL);
206 return pos;
209 static size_t percent(Vis *vis, Text *txt, size_t pos) {
210 int ratio = vis_count_get_default(vis, 0);
211 if (ratio > 100)
212 ratio = 100;
213 return text_size(txt) * ratio / 100;
216 static size_t byte(Vis *vis, Text *txt, size_t pos) {
217 pos = vis_count_get_default(vis, 0);
218 size_t max = text_size(txt);
219 return pos <= max ? pos : max;
222 static size_t byte_left(Vis *vis, Text *txt, size_t pos) {
223 size_t off = vis_count_get_default(vis, 1);
224 return off <= pos ? pos-off : 0;
227 static size_t byte_right(Vis *vis, Text *txt, size_t pos) {
228 size_t off = vis_count_get_default(vis, 1);
229 size_t new = pos + off;
230 size_t max = text_size(txt);
231 return new <= max && new > pos ? new : max;
234 void vis_motion_type(Vis *vis, enum VisMotionType type) {
235 vis->action.type = type;
238 int vis_motion_register(Vis *vis, void *data, VisMotionFunction *motion) {
240 Movement *move = calloc(1, sizeof *move);
241 if (!move)
242 return -1;
244 move->user = motion;
245 move->data = data;
247 if (array_add_ptr(&vis->motions, move))
248 return VIS_MOVE_LAST + array_length(&vis->motions) - 1;
249 free(move);
250 return -1;
253 bool vis_motion(Vis *vis, enum VisMotion motion, ...) {
254 va_list ap;
255 va_start(ap, motion);
257 switch (motion) {
258 case VIS_MOVE_WORD_START_NEXT:
259 if (vis->action.op == &vis_operators[VIS_OP_CHANGE])
260 motion = VIS_MOVE_WORD_NEXT;
261 break;
262 case VIS_MOVE_LONGWORD_START_NEXT:
263 if (vis->action.op == &vis_operators[VIS_OP_CHANGE])
264 motion = VIS_MOVE_LONGWORD_NEXT;
265 break;
266 case VIS_MOVE_SEARCH_FORWARD:
267 case VIS_MOVE_SEARCH_BACKWARD:
269 const char *pattern = va_arg(ap, char*);
270 Regex *regex = vis_regex(vis, pattern);
271 if (!regex) {
272 vis_cancel(vis);
273 goto err;
275 text_regex_free(regex);
276 if (motion == VIS_MOVE_SEARCH_FORWARD)
277 motion = VIS_MOVE_SEARCH_REPEAT_FORWARD;
278 else
279 motion = VIS_MOVE_SEARCH_REPEAT_BACKWARD;
280 vis->search_direction = motion;
281 break;
283 case VIS_MOVE_SEARCH_REPEAT:
284 case VIS_MOVE_SEARCH_REPEAT_REVERSE:
286 if (!vis->search_direction)
287 vis->search_direction = VIS_MOVE_SEARCH_REPEAT_FORWARD;
288 if (motion == VIS_MOVE_SEARCH_REPEAT) {
289 motion = vis->search_direction;
290 } else {
291 motion = vis->search_direction == VIS_MOVE_SEARCH_REPEAT_FORWARD ?
292 VIS_MOVE_SEARCH_REPEAT_BACKWARD :
293 VIS_MOVE_SEARCH_REPEAT_FORWARD;
295 break;
297 case VIS_MOVE_RIGHT_TO:
298 case VIS_MOVE_LEFT_TO:
299 case VIS_MOVE_RIGHT_TILL:
300 case VIS_MOVE_LEFT_TILL:
302 const char *key = va_arg(ap, char*);
303 if (!key)
304 goto err;
305 strncpy(vis->search_char, key, sizeof(vis->search_char));
306 vis->search_char[sizeof(vis->search_char)-1] = '\0';
307 vis->last_totill = motion;
308 break;
310 case VIS_MOVE_TOTILL_REPEAT:
311 if (!vis->last_totill)
312 goto err;
313 motion = vis->last_totill;
314 break;
315 case VIS_MOVE_TOTILL_REVERSE:
316 switch (vis->last_totill) {
317 case VIS_MOVE_RIGHT_TO:
318 motion = VIS_MOVE_LEFT_TO;
319 break;
320 case VIS_MOVE_LEFT_TO:
321 motion = VIS_MOVE_RIGHT_TO;
322 break;
323 case VIS_MOVE_RIGHT_TILL:
324 motion = VIS_MOVE_LEFT_TILL;
325 break;
326 case VIS_MOVE_LEFT_TILL:
327 motion = VIS_MOVE_RIGHT_TILL;
328 break;
329 default:
330 goto err;
332 break;
333 default:
334 break;
337 if (motion < LENGTH(vis_motions))
338 vis->action.movement = &vis_motions[motion];
339 else
340 vis->action.movement = array_get_ptr(&vis->motions, motion - VIS_MOVE_LAST);
342 if (!vis->action.movement)
343 goto err;
345 va_end(ap);
346 vis_do(vis);
347 return true;
348 err:
349 va_end(ap);
350 return false;
353 const Movement vis_motions[] = {
354 [VIS_MOVE_LINE_UP] = {
355 .cur = view_line_up,
356 .type = LINEWISE|LINEWISE_INCLUSIVE,
358 [VIS_MOVE_LINE_DOWN] = {
359 .cur = view_line_down,
360 .type = LINEWISE|LINEWISE_INCLUSIVE,
362 [VIS_MOVE_SCREEN_LINE_UP] = {
363 .cur = view_screenline_up,
365 [VIS_MOVE_SCREEN_LINE_DOWN] = {
366 .cur = view_screenline_down,
368 [VIS_MOVE_SCREEN_LINE_BEGIN] = {
369 .cur = view_screenline_begin,
370 .type = CHARWISE,
372 [VIS_MOVE_SCREEN_LINE_MIDDLE] = {
373 .cur = view_screenline_middle,
374 .type = CHARWISE,
376 [VIS_MOVE_SCREEN_LINE_END] = {
377 .cur = view_screenline_end,
378 .type = CHARWISE|INCLUSIVE,
380 [VIS_MOVE_LINE_PREV] = {
381 .txt = text_line_prev,
383 [VIS_MOVE_LINE_BEGIN] = {
384 .txt = text_line_begin,
385 .type = IDEMPOTENT,
387 [VIS_MOVE_LINE_START] = {
388 .txt = text_line_start,
389 .type = IDEMPOTENT,
391 [VIS_MOVE_LINE_FINISH] = {
392 .txt = text_line_finish,
393 .type = INCLUSIVE|IDEMPOTENT,
395 [VIS_MOVE_LINE_END] = {
396 .txt = text_line_end,
397 .type = IDEMPOTENT,
399 [VIS_MOVE_LINE_NEXT] = {
400 .txt = text_line_next,
402 [VIS_MOVE_LINE] = {
403 .vis = line,
404 .type = LINEWISE|IDEMPOTENT|JUMP,
406 [VIS_MOVE_COLUMN] = {
407 .vis = column,
408 .type = CHARWISE|IDEMPOTENT,
410 [VIS_MOVE_CHAR_PREV] = {
411 .txt = text_char_prev,
412 .type = CHARWISE,
414 [VIS_MOVE_CHAR_NEXT] = {
415 .txt = text_char_next,
416 .type = CHARWISE,
418 [VIS_MOVE_LINE_CHAR_PREV] = {
419 .txt = text_line_char_prev,
420 .type = CHARWISE,
422 [VIS_MOVE_LINE_CHAR_NEXT] = {
423 .txt = text_line_char_next,
424 .type = CHARWISE,
426 [VIS_MOVE_CODEPOINT_PREV] = {
427 .txt = text_codepoint_prev,
428 .type = CHARWISE,
430 [VIS_MOVE_CODEPOINT_NEXT] = {
431 .txt = text_codepoint_next,
432 .type = CHARWISE,
434 [VIS_MOVE_WORD_NEXT] = {
435 .vis = word_next,
436 .type = CHARWISE|IDEMPOTENT,
438 [VIS_MOVE_WORD_START_PREV] = {
439 .txt = text_word_start_prev,
440 .type = CHARWISE,
442 [VIS_MOVE_WORD_START_NEXT] = {
443 .txt = text_word_start_next,
444 .type = CHARWISE,
446 [VIS_MOVE_WORD_END_PREV] = {
447 .txt = text_word_end_prev,
448 .type = CHARWISE|INCLUSIVE,
450 [VIS_MOVE_WORD_END_NEXT] = {
451 .txt = text_word_end_next,
452 .type = CHARWISE|INCLUSIVE,
454 [VIS_MOVE_LONGWORD_NEXT] = {
455 .vis = longword_next,
456 .type = CHARWISE|IDEMPOTENT,
458 [VIS_MOVE_LONGWORD_START_PREV] = {
459 .txt = text_longword_start_prev,
460 .type = CHARWISE,
462 [VIS_MOVE_LONGWORD_START_NEXT] = {
463 .txt = text_longword_start_next,
464 .type = CHARWISE,
466 [VIS_MOVE_LONGWORD_END_PREV] = {
467 .txt = text_longword_end_prev,
468 .type = CHARWISE|INCLUSIVE,
470 [VIS_MOVE_LONGWORD_END_NEXT] = {
471 .txt = text_longword_end_next,
472 .type = CHARWISE|INCLUSIVE,
474 [VIS_MOVE_SENTENCE_PREV] = {
475 .txt = text_sentence_prev,
476 .type = CHARWISE,
478 [VIS_MOVE_SENTENCE_NEXT] = {
479 .txt = text_sentence_next,
480 .type = CHARWISE,
482 [VIS_MOVE_PARAGRAPH_PREV] = {
483 .txt = text_paragraph_prev,
484 .type = LINEWISE|JUMP,
486 [VIS_MOVE_PARAGRAPH_NEXT] = {
487 .txt = text_paragraph_next,
488 .type = LINEWISE|JUMP,
490 [VIS_MOVE_BLOCK_START] = {
491 .txt = text_block_start,
492 .type = JUMP,
494 [VIS_MOVE_BLOCK_END] = {
495 .txt = text_block_end,
496 .type = JUMP,
498 [VIS_MOVE_PARENTHESIS_START] = {
499 .txt = text_parenthesis_start,
500 .type = JUMP,
502 [VIS_MOVE_PARENTHESIS_END] = {
503 .txt = text_parenthesis_end,
504 .type = JUMP,
506 [VIS_MOVE_BRACKET_MATCH] = {
507 .txt = bracket_match,
508 .type = INCLUSIVE|JUMP,
510 [VIS_MOVE_FILE_BEGIN] = {
511 .txt = firstline,
512 .type = LINEWISE|LINEWISE_INCLUSIVE|JUMP|IDEMPOTENT,
514 [VIS_MOVE_FILE_END] = {
515 .txt = lastline,
516 .type = LINEWISE|LINEWISE_INCLUSIVE|JUMP|IDEMPOTENT,
518 [VIS_MOVE_LEFT_TO] = {
519 .vis = to_left,
520 .type = COUNT_EXACT,
522 [VIS_MOVE_RIGHT_TO] = {
523 .vis = to,
524 .type = INCLUSIVE|COUNT_EXACT,
526 [VIS_MOVE_LEFT_TILL] = {
527 .vis = till_left,
528 .type = COUNT_EXACT,
530 [VIS_MOVE_RIGHT_TILL] = {
531 .vis = till,
532 .type = INCLUSIVE|COUNT_EXACT,
534 [VIS_MOVE_SEARCH_WORD_FORWARD] = {
535 .vis = search_word_forward,
536 .type = JUMP,
538 [VIS_MOVE_SEARCH_WORD_BACKWARD] = {
539 .vis = search_word_backward,
540 .type = JUMP,
542 [VIS_MOVE_SEARCH_REPEAT_FORWARD] = {
543 .vis = search_forward,
544 .type = JUMP,
546 [VIS_MOVE_SEARCH_REPEAT_BACKWARD] = {
547 .vis = search_backward,
548 .type = JUMP,
550 [VIS_MOVE_WINDOW_LINE_TOP] = {
551 .view = view_lines_top,
552 .type = LINEWISE|JUMP|IDEMPOTENT,
554 [VIS_MOVE_WINDOW_LINE_MIDDLE] = {
555 .view = view_lines_middle,
556 .type = LINEWISE|JUMP|IDEMPOTENT,
558 [VIS_MOVE_WINDOW_LINE_BOTTOM] = {
559 .view = view_lines_bottom,
560 .type = LINEWISE|JUMP|IDEMPOTENT,
562 [VIS_MOVE_NOP] = {
563 .win = window_nop,
564 .type = IDEMPOTENT,
566 [VIS_MOVE_PERCENT] = {
567 .vis = percent,
568 .type = IDEMPOTENT,
570 [VIS_MOVE_BYTE] = {
571 .vis = byte,
572 .type = IDEMPOTENT,
574 [VIS_MOVE_BYTE_LEFT] = {
575 .vis = byte_left,
576 .type = IDEMPOTENT,
578 [VIS_MOVE_BYTE_RIGHT] = {
579 .vis = byte_right,
580 .type = IDEMPOTENT,