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
];
88 size_t newpos
= motion
->txt(txt
, pos
);
94 if (motion
->type
& INCLUSIVE
)
95 pos
= text_char_next(txt
, pos
);
100 static size_t word_next(Vis
*vis
, Text
*txt
, size_t pos
) {
101 return common_word_next(vis
, txt
, pos
, VIS_MOVE_WORD_END_NEXT
);
104 static size_t longword_next(Vis
*vis
, Text
*txt
, size_t pos
) {
105 return common_word_next(vis
, txt
, pos
, VIS_MOVE_LONGWORD_END_NEXT
);
108 static size_t mark_goto(Vis
*vis
, File
*file
, size_t pos
) {
109 return text_mark_get(file
->text
, file
->marks
[vis
->action
.mark
]);
112 static size_t mark_line_goto(Vis
*vis
, File
*file
, size_t pos
) {
113 return text_line_start(file
->text
, mark_goto(vis
, file
, pos
));
116 static size_t to(Vis
*vis
, Text
*txt
, size_t pos
) {
118 if (pos
== text_line_end(txt
, pos
))
120 size_t hit
= text_line_find_next(txt
, pos
+1, vis
->search_char
);
121 if (!text_byte_get(txt
, hit
, &c
) || c
!= vis
->search_char
[0])
126 static size_t till(Vis
*vis
, Text
*txt
, size_t pos
) {
127 size_t hit
= to(vis
, txt
, pos
+1);
128 if (pos
== text_line_end(txt
, pos
))
131 return text_char_prev(txt
, hit
);
135 static size_t to_left(Vis
*vis
, Text
*txt
, size_t pos
) {
136 return text_line_find_prev(txt
, pos
, vis
->search_char
);
139 static size_t till_left(Vis
*vis
, Text
*txt
, size_t pos
) {
140 if (pos
== text_line_begin(txt
, pos
))
142 size_t hit
= to_left(vis
, txt
, pos
-1);
144 return text_char_next(txt
, hit
);
148 static size_t firstline(Text
*txt
, size_t pos
) {
149 return text_line_start(txt
, 0);
152 static size_t line(Vis
*vis
, Text
*txt
, size_t pos
) {
153 int count
= vis_count_get_default(vis
, 1);
154 return text_line_start(txt
, text_pos_by_lineno(txt
, count
));
157 static size_t lastline(Text
*txt
, size_t pos
) {
158 pos
= text_size(txt
);
159 return text_line_start(txt
, pos
> 0 ? pos
-1 : pos
);
162 static size_t column(Vis
*vis
, Text
*txt
, size_t pos
) {
163 return text_line_offset(txt
, pos
, vis_count_get_default(vis
, 0));
166 static size_t view_lines_top(Vis
*vis
, View
*view
) {
167 return view_screenline_goto(view
, vis_count_get_default(vis
, 1));
170 static size_t view_lines_middle(Vis
*vis
, View
*view
) {
171 int h
= view_height_get(view
);
172 return view_screenline_goto(view
, h
/2);
175 static size_t view_lines_bottom(Vis
*vis
, View
*view
) {
176 int h
= view_height_get(vis
->win
->view
);
177 return view_screenline_goto(vis
->win
->view
, h
- vis_count_get_default(vis
, 0));
180 static size_t window_changelist_next(Vis
*vis
, Win
*win
, size_t pos
) {
181 ChangeList
*cl
= &win
->changelist
;
182 Text
*txt
= win
->file
->text
;
183 time_t state
= text_state(txt
);
184 if (cl
->state
!= state
)
186 else if (cl
->index
> 0 && pos
== cl
->pos
)
188 size_t newpos
= text_history_get(txt
, cl
->index
);
197 static size_t window_changelist_prev(Vis
*vis
, Win
*win
, size_t pos
) {
198 ChangeList
*cl
= &win
->changelist
;
199 Text
*txt
= win
->file
->text
;
200 time_t state
= text_state(txt
);
201 if (cl
->state
!= state
)
203 else if (pos
== cl
->pos
)
204 win
->changelist
.index
++;
205 size_t newpos
= text_history_get(txt
, cl
->index
);
214 static size_t window_jumplist_next(Vis
*vis
, Win
*win
, size_t cur
) {
215 while (win
->jumplist
) {
216 Mark mark
= (Mark
)ringbuf_next(win
->jumplist
);
219 size_t pos
= text_mark_get(win
->file
->text
, mark
);
220 if (pos
!= EPOS
&& pos
!= cur
)
226 static size_t window_jumplist_prev(Vis
*vis
, Win
*win
, size_t cur
) {
227 while (win
->jumplist
) {
228 Mark mark
= (Mark
)ringbuf_prev(win
->jumplist
);
231 size_t pos
= text_mark_get(win
->file
->text
, mark
);
232 if (pos
!= EPOS
&& pos
!= cur
)
238 static size_t window_nop(Vis
*vis
, Win
*win
, size_t pos
) {
242 static size_t bracket_match(Text
*txt
, size_t pos
) {
243 size_t hit
= text_bracket_match_symbol(txt
, pos
, "(){}[]<>");
247 Iterator it
= text_iterator_get(txt
, pos
);
248 while (text_iterator_byte_get(&it
, ¤t
)) {
260 text_iterator_byte_next(&it
, NULL
);
265 static size_t percent(Vis
*vis
, Text
*txt
, size_t pos
) {
266 int ratio
= vis_count_get_default(vis
, 0);
269 return text_size(txt
) * ratio
/ 100;
272 static size_t byte(Vis
*vis
, Text
*txt
, size_t pos
) {
273 pos
= vis_count_get_default(vis
, 0);
274 size_t max
= text_size(txt
);
275 return pos
<= max
? pos
: max
;
278 static size_t byte_left(Vis
*vis
, Text
*txt
, size_t pos
) {
279 size_t off
= vis_count_get_default(vis
, 1);
280 return off
<= pos
? pos
-off
: 0;
283 static size_t byte_right(Vis
*vis
, Text
*txt
, size_t pos
) {
284 size_t off
= vis_count_get_default(vis
, 1);
285 size_t new = pos
+ off
;
286 size_t max
= text_size(txt
);
287 return new <= max
&& new > pos
? new : max
;
290 void vis_motion_type(Vis
*vis
, enum VisMotionType type
) {
291 vis
->action
.type
= type
;
294 int vis_motion_register(Vis
*vis
, enum VisMotionType type
, void *data
,
295 size_t (*motion
)(Vis
*, Win
*, void*, size_t pos
)) {
297 Movement
*move
= calloc(1, sizeof *move
);
305 if (array_add_ptr(&vis
->motions
, move
))
306 return VIS_MOVE_LAST
+ array_length(&vis
->motions
) - 1;
311 bool vis_motion(Vis
*vis
, enum VisMotion motion
, ...) {
313 va_start(ap
, motion
);
316 case VIS_MOVE_WORD_START_NEXT
:
317 if (vis
->action
.op
== &vis_operators
[VIS_OP_CHANGE
])
318 motion
= VIS_MOVE_WORD_NEXT
;
320 case VIS_MOVE_LONGWORD_START_NEXT
:
321 if (vis
->action
.op
== &vis_operators
[VIS_OP_CHANGE
])
322 motion
= VIS_MOVE_LONGWORD_NEXT
;
324 case VIS_MOVE_SEARCH_FORWARD
:
325 case VIS_MOVE_SEARCH_BACKWARD
:
327 const char *pattern
= va_arg(ap
, char*);
328 Regex
*regex
= vis_regex(vis
, pattern
);
333 text_regex_free(regex
);
334 if (motion
== VIS_MOVE_SEARCH_FORWARD
)
335 motion
= VIS_MOVE_SEARCH_REPEAT_FORWARD
;
337 motion
= VIS_MOVE_SEARCH_REPEAT_BACKWARD
;
338 vis
->search_direction
= motion
;
341 case VIS_MOVE_SEARCH_REPEAT
:
342 case VIS_MOVE_SEARCH_REPEAT_REVERSE
:
344 if (!vis
->search_direction
)
345 vis
->search_direction
= VIS_MOVE_SEARCH_REPEAT_FORWARD
;
346 if (motion
== VIS_MOVE_SEARCH_REPEAT
) {
347 motion
= vis
->search_direction
;
349 motion
= vis
->search_direction
== VIS_MOVE_SEARCH_REPEAT_FORWARD
?
350 VIS_MOVE_SEARCH_REPEAT_BACKWARD
:
351 VIS_MOVE_SEARCH_REPEAT_FORWARD
;
355 case VIS_MOVE_RIGHT_TO
:
356 case VIS_MOVE_LEFT_TO
:
357 case VIS_MOVE_RIGHT_TILL
:
358 case VIS_MOVE_LEFT_TILL
:
360 const char *key
= va_arg(ap
, char*);
363 strncpy(vis
->search_char
, key
, sizeof(vis
->search_char
));
364 vis
->search_char
[sizeof(vis
->search_char
)-1] = '\0';
365 vis
->last_totill
= motion
;
368 case VIS_MOVE_TOTILL_REPEAT
:
369 if (!vis
->last_totill
)
371 motion
= vis
->last_totill
;
373 case VIS_MOVE_TOTILL_REVERSE
:
374 switch (vis
->last_totill
) {
375 case VIS_MOVE_RIGHT_TO
:
376 motion
= VIS_MOVE_LEFT_TO
;
378 case VIS_MOVE_LEFT_TO
:
379 motion
= VIS_MOVE_RIGHT_TO
;
381 case VIS_MOVE_RIGHT_TILL
:
382 motion
= VIS_MOVE_LEFT_TILL
;
384 case VIS_MOVE_LEFT_TILL
:
385 motion
= VIS_MOVE_RIGHT_TILL
;
392 case VIS_MOVE_MARK_LINE
:
394 int mark
= va_arg(ap
, int);
395 if (VIS_MARK_a
<= mark
&& mark
< VIS_MARK_INVALID
)
396 vis
->action
.mark
= mark
;
405 if (motion
< LENGTH(vis_motions
))
406 vis
->action
.movement
= &vis_motions
[motion
];
408 vis
->action
.movement
= array_get_ptr(&vis
->motions
, motion
- VIS_MOVE_LAST
);
410 if (!vis
->action
.movement
)
421 const Movement vis_motions
[] = {
422 [VIS_MOVE_LINE_UP
] = {
424 .type
= LINEWISE
|LINEWISE_INCLUSIVE
,
426 [VIS_MOVE_LINE_DOWN
] = {
427 .cur
= view_line_down
,
428 .type
= LINEWISE
|LINEWISE_INCLUSIVE
,
430 [VIS_MOVE_SCREEN_LINE_UP
] = {
431 .cur
= view_screenline_up
,
433 [VIS_MOVE_SCREEN_LINE_DOWN
] = {
434 .cur
= view_screenline_down
,
436 [VIS_MOVE_SCREEN_LINE_BEGIN
] = {
437 .cur
= view_screenline_begin
,
440 [VIS_MOVE_SCREEN_LINE_MIDDLE
] = {
441 .cur
= view_screenline_middle
,
444 [VIS_MOVE_SCREEN_LINE_END
] = {
445 .cur
= view_screenline_end
,
446 .type
= CHARWISE
|INCLUSIVE
,
448 [VIS_MOVE_LINE_PREV
] = {
449 .txt
= text_line_prev
,
451 [VIS_MOVE_LINE_BEGIN
] = {
452 .txt
= text_line_begin
,
455 [VIS_MOVE_LINE_START
] = {
456 .txt
= text_line_start
,
459 [VIS_MOVE_LINE_FINISH
] = {
460 .txt
= text_line_finish
,
461 .type
= INCLUSIVE
|IDEMPOTENT
,
463 [VIS_MOVE_LINE_END
] = {
464 .txt
= text_line_end
,
467 [VIS_MOVE_LINE_NEXT
] = {
468 .txt
= text_line_next
,
472 .type
= LINEWISE
|IDEMPOTENT
|JUMP
,
474 [VIS_MOVE_COLUMN
] = {
476 .type
= CHARWISE
|IDEMPOTENT
,
478 [VIS_MOVE_CHAR_PREV
] = {
479 .txt
= text_char_prev
,
482 [VIS_MOVE_CHAR_NEXT
] = {
483 .txt
= text_char_next
,
486 [VIS_MOVE_LINE_CHAR_PREV
] = {
487 .txt
= text_line_char_prev
,
490 [VIS_MOVE_LINE_CHAR_NEXT
] = {
491 .txt
= text_line_char_next
,
494 [VIS_MOVE_CODEPOINT_PREV
] = {
495 .txt
= text_codepoint_prev
,
498 [VIS_MOVE_CODEPOINT_NEXT
] = {
499 .txt
= text_codepoint_next
,
502 [VIS_MOVE_WORD_NEXT
] = {
504 .type
= CHARWISE
|IDEMPOTENT
,
506 [VIS_MOVE_WORD_START_PREV
] = {
507 .txt
= text_word_start_prev
,
510 [VIS_MOVE_WORD_START_NEXT
] = {
511 .txt
= text_word_start_next
,
514 [VIS_MOVE_WORD_END_PREV
] = {
515 .txt
= text_word_end_prev
,
516 .type
= CHARWISE
|INCLUSIVE
,
518 [VIS_MOVE_WORD_END_NEXT
] = {
519 .txt
= text_word_end_next
,
520 .type
= CHARWISE
|INCLUSIVE
,
522 [VIS_MOVE_LONGWORD_NEXT
] = {
523 .vis
= longword_next
,
524 .type
= CHARWISE
|IDEMPOTENT
,
526 [VIS_MOVE_LONGWORD_START_PREV
] = {
527 .txt
= text_longword_start_prev
,
530 [VIS_MOVE_LONGWORD_START_NEXT
] = {
531 .txt
= text_longword_start_next
,
534 [VIS_MOVE_LONGWORD_END_PREV
] = {
535 .txt
= text_longword_end_prev
,
536 .type
= CHARWISE
|INCLUSIVE
,
538 [VIS_MOVE_LONGWORD_END_NEXT
] = {
539 .txt
= text_longword_end_next
,
540 .type
= CHARWISE
|INCLUSIVE
,
542 [VIS_MOVE_SENTENCE_PREV
] = {
543 .txt
= text_sentence_prev
,
546 [VIS_MOVE_SENTENCE_NEXT
] = {
547 .txt
= text_sentence_next
,
550 [VIS_MOVE_PARAGRAPH_PREV
] = {
551 .txt
= text_paragraph_prev
,
552 .type
= LINEWISE
|JUMP
,
554 [VIS_MOVE_PARAGRAPH_NEXT
] = {
555 .txt
= text_paragraph_next
,
556 .type
= LINEWISE
|JUMP
,
558 [VIS_MOVE_BLOCK_START
] = {
559 .txt
= text_block_start
,
562 [VIS_MOVE_BLOCK_END
] = {
563 .txt
= text_block_end
,
566 [VIS_MOVE_PARENTHESE_START
] = {
567 .txt
= text_parenthese_start
,
570 [VIS_MOVE_PARENTHESE_END
] = {
571 .txt
= text_parenthese_end
,
574 [VIS_MOVE_BRACKET_MATCH
] = {
575 .txt
= bracket_match
,
576 .type
= INCLUSIVE
|JUMP
,
578 [VIS_MOVE_FILE_BEGIN
] = {
580 .type
= LINEWISE
|LINEWISE_INCLUSIVE
|JUMP
|IDEMPOTENT
,
582 [VIS_MOVE_FILE_END
] = {
584 .type
= LINEWISE
|LINEWISE_INCLUSIVE
|JUMP
|IDEMPOTENT
,
586 [VIS_MOVE_LEFT_TO
] = {
590 [VIS_MOVE_RIGHT_TO
] = {
592 .type
= INCLUSIVE
|COUNT_EXACT
,
594 [VIS_MOVE_LEFT_TILL
] = {
598 [VIS_MOVE_RIGHT_TILL
] = {
600 .type
= INCLUSIVE
|COUNT_EXACT
,
604 .type
= JUMP
|IDEMPOTENT
,
606 [VIS_MOVE_MARK_LINE
] = {
607 .file
= mark_line_goto
,
608 .type
= LINEWISE
|JUMP
|IDEMPOTENT
,
610 [VIS_MOVE_SEARCH_WORD_FORWARD
] = {
611 .vis
= search_word_forward
,
614 [VIS_MOVE_SEARCH_WORD_BACKWARD
] = {
615 .vis
= search_word_backward
,
618 [VIS_MOVE_SEARCH_REPEAT_FORWARD
] = {
619 .vis
= search_forward
,
622 [VIS_MOVE_SEARCH_REPEAT_BACKWARD
] = {
623 .vis
= search_backward
,
626 [VIS_MOVE_WINDOW_LINE_TOP
] = {
627 .view
= view_lines_top
,
628 .type
= LINEWISE
|JUMP
|IDEMPOTENT
,
630 [VIS_MOVE_WINDOW_LINE_MIDDLE
] = {
631 .view
= view_lines_middle
,
632 .type
= LINEWISE
|JUMP
|IDEMPOTENT
,
634 [VIS_MOVE_WINDOW_LINE_BOTTOM
] = {
635 .view
= view_lines_bottom
,
636 .type
= LINEWISE
|JUMP
|IDEMPOTENT
,
638 [VIS_MOVE_CHANGELIST_NEXT
] = {
639 .win
= window_changelist_next
,
642 [VIS_MOVE_CHANGELIST_PREV
] = {
643 .win
= window_changelist_prev
,
646 [VIS_MOVE_JUMPLIST_NEXT
] = {
647 .win
= window_jumplist_next
,
650 [VIS_MOVE_JUMPLIST_PREV
] = {
651 .win
= window_jumplist_prev
,
658 [VIS_MOVE_PERCENT
] = {
666 [VIS_MOVE_BYTE_LEFT
] = {
670 [VIS_MOVE_BYTE_RIGHT
] = {