4 #include "text-motions.h"
5 #include "text-objects.h"
9 static Regex
*search_word(Vis
*vis
, Text
*txt
, size_t pos
) {
11 Filerange word
= text_object_word(txt
, pos
);
12 if (!text_range_valid(&word
))
14 char *buf
= text_bytes_alloc0(txt
, word
.start
, text_range_size(&word
));
17 snprintf(expr
, sizeof(expr
), "[[:<:]]%s[[:>:]]", buf
);
18 Regex
*regex
= vis_regex(vis
, expr
);
20 snprintf(expr
, sizeof(expr
), "\\<%s\\>", buf
);
21 regex
= vis_regex(vis
, expr
);
27 static size_t search_word_forward(Vis
*vis
, Text
*txt
, size_t pos
) {
28 Regex
*regex
= search_word(vis
, txt
, pos
);
30 vis
->search_direction
= VIS_MOVE_SEARCH_REPEAT_FORWARD
;
31 pos
= text_search_forward(txt
, pos
, regex
);
33 text_regex_free(regex
);
37 static size_t search_word_backward(Vis
*vis
, Text
*txt
, size_t pos
) {
38 Regex
*regex
= search_word(vis
, txt
, pos
);
40 vis
->search_direction
= VIS_MOVE_SEARCH_REPEAT_BACKWARD
;
41 pos
= text_search_backward(txt
, pos
, regex
);
43 text_regex_free(regex
);
47 static size_t search_forward(Vis
*vis
, Text
*txt
, size_t pos
) {
48 Regex
*regex
= vis_regex(vis
, NULL
);
50 pos
= text_search_forward(txt
, pos
, regex
);
51 text_regex_free(regex
);
55 static size_t search_backward(Vis
*vis
, Text
*txt
, size_t pos
) {
56 Regex
*regex
= vis_regex(vis
, NULL
);
58 pos
= text_search_backward(txt
, pos
, regex
);
59 text_regex_free(regex
);
63 static size_t common_word_next(Vis
*vis
, Text
*txt
, size_t pos
, enum VisMotion end_next
) {
65 Iterator it
= text_iterator_get(txt
, pos
);
66 if (!text_iterator_byte_get(&it
, &c
))
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 */
75 /* map `cw` to `cl` */
76 motion
= &vis_motions
[VIS_MOVE_CHAR_NEXT
];
78 /* map `c{n}w` to `c{n-1}e` */
80 motion
= &vis_motions
[end_next
];
83 /* map `cw` to `ce` */
84 motion
= &vis_motions
[end_next
];
90 size_t newpos
= motion
->txt(txt
, pos
);
96 if (motion
->type
& INCLUSIVE
)
97 pos
= text_char_next(txt
, 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
) {
120 if (pos
== text_line_end(txt
, 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])
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
))
133 return text_char_prev(txt
, hit
);
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
))
144 size_t hit
= to_left(vis
, txt
, pos
-1);
146 return text_char_next(txt
, hit
);
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
)
188 else if (cl
->index
> 0 && pos
== 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
)
205 else if (pos
== cl
->pos
)
206 win
->changelist
.index
++;
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
);
221 size_t pos
= text_mark_get(win
->file
->text
, mark
);
222 if (pos
!= EPOS
&& pos
!= 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
);
233 size_t pos
= text_mark_get(win
->file
->text
, mark
);
234 if (pos
!= EPOS
&& pos
!= cur
)
240 static size_t window_nop(Vis
*vis
, Win
*win
, size_t pos
) {
244 static size_t bracket_match(Text
*txt
, size_t pos
) {
245 size_t hit
= text_bracket_match_symbol(txt
, pos
, "(){}[]<>");
249 Iterator it
= text_iterator_get(txt
, pos
);
250 while (text_iterator_byte_get(&it
, ¤t
)) {
262 text_iterator_byte_next(&it
, NULL
);
267 static size_t percent(Vis
*vis
, Text
*txt
, size_t pos
) {
268 int ratio
= vis_count_get_default(vis
, 0);
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
);
306 if (array_add_ptr(&vis
->motions
, move
))
307 return VIS_MOVE_LAST
+ array_length(&vis
->motions
) - 1;
312 bool vis_motion(Vis
*vis
, enum VisMotion motion
, ...) {
314 va_start(ap
, motion
);
317 case VIS_MOVE_WORD_START_NEXT
:
318 if (vis
->action
.op
== &vis_operators
[VIS_OP_CHANGE
])
319 motion
= VIS_MOVE_WORD_NEXT
;
321 case VIS_MOVE_LONGWORD_START_NEXT
:
322 if (vis
->action
.op
== &vis_operators
[VIS_OP_CHANGE
])
323 motion
= VIS_MOVE_LONGWORD_NEXT
;
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
);
334 text_regex_free(regex
);
335 if (motion
== VIS_MOVE_SEARCH_FORWARD
)
336 motion
= VIS_MOVE_SEARCH_REPEAT_FORWARD
;
338 motion
= VIS_MOVE_SEARCH_REPEAT_BACKWARD
;
339 vis
->search_direction
= motion
;
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
;
350 motion
= vis
->search_direction
== VIS_MOVE_SEARCH_REPEAT_FORWARD
?
351 VIS_MOVE_SEARCH_REPEAT_BACKWARD
:
352 VIS_MOVE_SEARCH_REPEAT_FORWARD
;
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*);
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
;
369 case VIS_MOVE_TOTILL_REPEAT
:
370 if (!vis
->last_totill
)
372 motion
= vis
->last_totill
;
374 case VIS_MOVE_TOTILL_REVERSE
:
375 switch (vis
->last_totill
) {
376 case VIS_MOVE_RIGHT_TO
:
377 motion
= VIS_MOVE_LEFT_TO
;
379 case VIS_MOVE_LEFT_TO
:
380 motion
= VIS_MOVE_RIGHT_TO
;
382 case VIS_MOVE_RIGHT_TILL
:
383 motion
= VIS_MOVE_LEFT_TILL
;
385 case VIS_MOVE_LEFT_TILL
:
386 motion
= VIS_MOVE_RIGHT_TILL
;
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
;
406 if (motion
< LENGTH(vis_motions
))
407 vis
->action
.movement
= &vis_motions
[motion
];
409 vis
->action
.movement
= array_get_ptr(&vis
->motions
, motion
- VIS_MOVE_LAST
);
411 if (!vis
->action
.movement
)
422 const Movement vis_motions
[] = {
423 [VIS_MOVE_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
,
441 [VIS_MOVE_SCREEN_LINE_MIDDLE
] = {
442 .cur
= view_screenline_middle
,
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
,
456 [VIS_MOVE_LINE_START
] = {
457 .txt
= text_line_start
,
460 [VIS_MOVE_LINE_FINISH
] = {
461 .txt
= text_line_finish
,
462 .type
= INCLUSIVE
|IDEMPOTENT
,
464 [VIS_MOVE_LINE_END
] = {
465 .txt
= text_line_end
,
468 [VIS_MOVE_LINE_NEXT
] = {
469 .txt
= text_line_next
,
473 .type
= LINEWISE
|IDEMPOTENT
|JUMP
,
475 [VIS_MOVE_COLUMN
] = {
477 .type
= CHARWISE
|IDEMPOTENT
,
479 [VIS_MOVE_CHAR_PREV
] = {
480 .txt
= text_char_prev
,
483 [VIS_MOVE_CHAR_NEXT
] = {
484 .txt
= text_char_next
,
487 [VIS_MOVE_LINE_CHAR_PREV
] = {
488 .txt
= text_line_char_prev
,
491 [VIS_MOVE_LINE_CHAR_NEXT
] = {
492 .txt
= text_line_char_next
,
495 [VIS_MOVE_CODEPOINT_PREV
] = {
496 .txt
= text_codepoint_prev
,
499 [VIS_MOVE_CODEPOINT_NEXT
] = {
500 .txt
= text_codepoint_next
,
503 [VIS_MOVE_WORD_NEXT
] = {
505 .type
= CHARWISE
|IDEMPOTENT
,
507 [VIS_MOVE_WORD_START_PREV
] = {
508 .txt
= text_word_start_prev
,
511 [VIS_MOVE_WORD_START_NEXT
] = {
512 .txt
= text_word_start_next
,
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
,
531 [VIS_MOVE_LONGWORD_START_NEXT
] = {
532 .txt
= text_longword_start_next
,
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
,
547 [VIS_MOVE_SENTENCE_NEXT
] = {
548 .txt
= text_sentence_next
,
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
,
563 [VIS_MOVE_BLOCK_END
] = {
564 .txt
= text_block_end
,
567 [VIS_MOVE_PARENTHESE_START
] = {
568 .txt
= text_parenthese_start
,
571 [VIS_MOVE_PARENTHESE_END
] = {
572 .txt
= text_parenthese_end
,
575 [VIS_MOVE_BRACKET_MATCH
] = {
576 .txt
= bracket_match
,
577 .type
= INCLUSIVE
|JUMP
,
579 [VIS_MOVE_FILE_BEGIN
] = {
581 .type
= LINEWISE
|LINEWISE_INCLUSIVE
|JUMP
|IDEMPOTENT
,
583 [VIS_MOVE_FILE_END
] = {
585 .type
= LINEWISE
|LINEWISE_INCLUSIVE
|JUMP
|IDEMPOTENT
,
587 [VIS_MOVE_LEFT_TO
] = {
591 [VIS_MOVE_RIGHT_TO
] = {
593 .type
= INCLUSIVE
|COUNT_EXACT
,
595 [VIS_MOVE_LEFT_TILL
] = {
599 [VIS_MOVE_RIGHT_TILL
] = {
601 .type
= INCLUSIVE
|COUNT_EXACT
,
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
,
615 [VIS_MOVE_SEARCH_WORD_BACKWARD
] = {
616 .vis
= search_word_backward
,
619 [VIS_MOVE_SEARCH_REPEAT_FORWARD
] = {
620 .vis
= search_forward
,
623 [VIS_MOVE_SEARCH_REPEAT_BACKWARD
] = {
624 .vis
= search_backward
,
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
,
643 [VIS_MOVE_CHANGELIST_PREV
] = {
644 .win
= window_changelist_prev
,
647 [VIS_MOVE_JUMPLIST_NEXT
] = {
648 .win
= window_jumplist_next
,
651 [VIS_MOVE_JUMPLIST_PREV
] = {
652 .win
= window_jumplist_prev
,
659 [VIS_MOVE_PERCENT
] = {
667 [VIS_MOVE_BYTE_LEFT
] = {
671 [VIS_MOVE_BYTE_RIGHT
] = {