use EXIT_FAILURE for exit status
[vis.git] / vis-motions.c
blobb5d9255f2bb2764d2b4e4787d3d09c7f9d836bba
1 #include <stdio.h>
2 #include <string.h>
3 #include <regex.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 /** utility functions */
12 static Regex *search_word(Vis *vis, Text *txt, size_t pos) {
13 char expr[512];
14 Filerange word = text_object_word(txt, pos);
15 if (!text_range_valid(&word))
16 return NULL;
17 char *buf = text_bytes_alloc0(txt, word.start, text_range_size(&word));
18 if (!buf)
19 return NULL;
20 snprintf(expr, sizeof(expr), "[[:<:]]%s[[:>:]]", buf);
21 Regex *regex = vis_regex(vis, expr);
22 if (!regex) {
23 snprintf(expr, sizeof(expr), "\\<%s\\>", buf);
24 regex = vis_regex(vis, expr);
26 free(buf);
27 return regex;
30 /** motion implementations */
32 static size_t search_word_forward(Vis *vis, Text *txt, size_t pos) {
33 Regex *regex = search_word(vis, txt, pos);
34 if (regex)
35 pos = text_search_forward(txt, pos, regex);
36 text_regex_free(regex);
37 return pos;
40 static size_t search_word_backward(Vis *vis, Text *txt, size_t pos) {
41 Regex *regex = search_word(vis, txt, pos);
42 if (regex)
43 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 mark_goto(Vis *vis, File *file, size_t pos) {
65 return text_mark_get(file->text, file->marks[vis->action.mark]);
68 static size_t mark_line_goto(Vis *vis, File *file, size_t pos) {
69 return text_line_start(file->text, mark_goto(vis, file, pos));
72 static size_t to(Vis *vis, Text *txt, size_t pos) {
73 char c;
74 if (pos == text_line_end(txt, pos))
75 return pos;
76 size_t hit = text_line_find_next(txt, pos+1, vis->search_char);
77 if (!text_byte_get(txt, hit, &c) || c != vis->search_char[0])
78 return pos;
79 return hit;
82 static size_t till(Vis *vis, Text *txt, size_t pos) {
83 size_t hit = to(vis, txt, pos+1);
84 if (pos == text_line_end(txt, pos))
85 return pos;
86 if (hit != pos)
87 return text_char_prev(txt, hit);
88 return pos;
91 static size_t to_left(Vis *vis, Text *txt, size_t pos) {
92 return text_line_find_prev(txt, pos, vis->search_char);
95 static size_t till_left(Vis *vis, Text *txt, size_t pos) {
96 if (pos == text_line_begin(txt, pos))
97 return pos;
98 size_t hit = to_left(vis, txt, pos-1);
99 if (hit != pos-1)
100 return text_char_next(txt, hit);
101 return pos;
104 static size_t line(Vis *vis, Text *txt, size_t pos) {
105 return text_pos_by_lineno(txt, vis_count_get_default(vis, 1));
108 static size_t column(Vis *vis, Text *txt, size_t pos) {
109 return text_line_offset(txt, pos, vis_count_get_default(vis, 0));
112 static size_t view_lines_top(Vis *vis, View *view) {
113 return view_screenline_goto(view, vis_count_get_default(vis, 1));
116 static size_t view_lines_middle(Vis *vis, View *view) {
117 int h = view_height_get(view);
118 return view_screenline_goto(view, h/2);
121 static size_t view_lines_bottom(Vis *vis, View *view) {
122 int h = view_height_get(vis->win->view);
123 return view_screenline_goto(vis->win->view, h - vis_count_get_default(vis, 0));
126 static size_t window_changelist_next(Vis *vis, Win *win, size_t pos) {
127 ChangeList *cl = &win->changelist;
128 Text *txt = win->file->text;
129 time_t state = text_state(txt);
130 if (cl->state != state)
131 cl->index = 0;
132 else if (cl->index > 0 && pos == cl->pos)
133 cl->index--;
134 size_t newpos = text_history_get(txt, cl->index);
135 if (newpos == EPOS)
136 cl->index++;
137 else
138 cl->pos = newpos;
139 cl->state = state;
140 return cl->pos;
143 static size_t window_changelist_prev(Vis *vis, Win *win, size_t pos) {
144 ChangeList *cl = &win->changelist;
145 Text *txt = win->file->text;
146 time_t state = text_state(txt);
147 if (cl->state != state)
148 cl->index = 0;
149 else if (pos == cl->pos)
150 win->changelist.index++;
151 size_t newpos = text_history_get(txt, cl->index);
152 if (newpos == EPOS)
153 cl->index--;
154 else
155 cl->pos = newpos;
156 cl->state = state;
157 return cl->pos;
160 static size_t window_jumplist_next(Vis *vis, Win *win, size_t cur) {
161 while (win->jumplist) {
162 Mark mark = ringbuf_next(win->jumplist);
163 if (!mark)
164 return cur;
165 size_t pos = text_mark_get(win->file->text, mark);
166 if (pos != EPOS && pos != cur)
167 return pos;
169 return cur;
172 static size_t window_jumplist_prev(Vis *vis, Win *win, size_t cur) {
173 while (win->jumplist) {
174 Mark mark = ringbuf_prev(win->jumplist);
175 if (!mark)
176 return cur;
177 size_t pos = text_mark_get(win->file->text, mark);
178 if (pos != EPOS && pos != cur)
179 return pos;
181 return cur;
184 static size_t window_nop(Vis *vis, Win *win, size_t pos) {
185 return pos;
188 static size_t bracket_match(Text *txt, size_t pos) {
189 size_t hit = text_bracket_match_symbol(txt, pos, "(){}[]<>");
190 if (hit != pos)
191 return hit;
192 char current;
193 Iterator it = text_iterator_get(txt, pos);
194 while (text_iterator_byte_get(&it, &current)) {
195 switch (current) {
196 case '(':
197 case ')':
198 case '{':
199 case '}':
200 case '[':
201 case ']':
202 case '<':
203 case '>':
204 return it.pos;
206 text_iterator_byte_next(&it, NULL);
208 return pos;
211 static size_t percent(Vis *vis, Text *txt, size_t pos) {
212 int ratio = vis_count_get_default(vis, 0);
213 if (ratio > 100)
214 ratio = 100;
215 return text_size(txt) * ratio / 100;
218 void vis_motion_type(Vis *vis, enum VisMotionType type) {
219 vis->action.type = type;
222 int vis_motion_register(Vis *vis, enum VisMotionType type, void *data,
223 size_t (*motion)(Vis*, Win*, void*, size_t pos)) {
225 Movement *move = calloc(1, sizeof *move);
226 if (!move)
227 return -1;
229 move->user = motion;
230 move->type = type;
231 move->data = data;
233 if (array_add_ptr(&vis->motions, move))
234 return VIS_MOVE_LAST + array_length(&vis->motions) - 1;
235 free(move);
236 return -1;
239 bool vis_motion(Vis *vis, enum VisMotion motion, ...) {
240 va_list ap;
241 va_start(ap, motion);
243 switch (motion) {
244 case VIS_MOVE_WORD_START_NEXT:
245 if (vis->action.op == &vis_operators[VIS_OP_CHANGE])
246 motion = VIS_MOVE_WORD_END_NEXT;
247 break;
248 case VIS_MOVE_LONGWORD_START_NEXT:
249 if (vis->action.op == &vis_operators[VIS_OP_CHANGE])
250 motion = VIS_MOVE_LONGWORD_END_NEXT;
251 break;
252 case VIS_MOVE_SEARCH_FORWARD:
253 case VIS_MOVE_SEARCH_BACKWARD:
255 const char *pattern = va_arg(ap, char*);
256 Regex *regex = vis_regex(vis, pattern);
257 if (!regex) {
258 vis_cancel(vis);
259 goto err;
261 text_regex_free(regex);
262 if (motion == VIS_MOVE_SEARCH_FORWARD)
263 motion = VIS_MOVE_SEARCH_NEXT;
264 else
265 motion = VIS_MOVE_SEARCH_PREV;
266 break;
268 case VIS_MOVE_RIGHT_TO:
269 case VIS_MOVE_LEFT_TO:
270 case VIS_MOVE_RIGHT_TILL:
271 case VIS_MOVE_LEFT_TILL:
273 const char *key = va_arg(ap, char*);
274 if (!key)
275 goto err;
276 strncpy(vis->search_char, key, sizeof(vis->search_char));
277 vis->search_char[sizeof(vis->search_char)-1] = '\0';
278 vis->last_totill = motion;
279 break;
281 case VIS_MOVE_TOTILL_REPEAT:
282 if (!vis->last_totill)
283 goto err;
284 motion = vis->last_totill;
285 break;
286 case VIS_MOVE_TOTILL_REVERSE:
287 switch (vis->last_totill) {
288 case VIS_MOVE_RIGHT_TO:
289 motion = VIS_MOVE_LEFT_TO;
290 break;
291 case VIS_MOVE_LEFT_TO:
292 motion = VIS_MOVE_RIGHT_TO;
293 break;
294 case VIS_MOVE_RIGHT_TILL:
295 motion = VIS_MOVE_LEFT_TILL;
296 break;
297 case VIS_MOVE_LEFT_TILL:
298 motion = VIS_MOVE_RIGHT_TILL;
299 break;
300 default:
301 goto err;
303 break;
304 case VIS_MOVE_MARK:
305 case VIS_MOVE_MARK_LINE:
307 int mark = va_arg(ap, int);
308 if (VIS_MARK_a <= mark && mark < VIS_MARK_INVALID)
309 vis->action.mark = mark;
310 else
311 goto err;
312 break;
314 default:
315 break;
318 if (motion < LENGTH(vis_motions))
319 vis->action.movement = &vis_motions[motion];
320 else
321 vis->action.movement = array_get_ptr(&vis->motions, motion - VIS_MOVE_LAST);
323 if (!vis->action.movement)
324 goto err;
326 va_end(ap);
327 vis_do(vis);
328 return true;
329 err:
330 va_end(ap);
331 return false;
334 const Movement vis_motions[] = {
335 [VIS_MOVE_LINE_UP] = { .cur = view_line_up, .type = LINEWISE|LINEWISE_INCLUSIVE },
336 [VIS_MOVE_LINE_DOWN] = { .cur = view_line_down, .type = LINEWISE|LINEWISE_INCLUSIVE },
337 [VIS_MOVE_SCREEN_LINE_UP] = { .cur = view_screenline_up, },
338 [VIS_MOVE_SCREEN_LINE_DOWN] = { .cur = view_screenline_down, },
339 [VIS_MOVE_SCREEN_LINE_BEGIN] = { .cur = view_screenline_begin, .type = CHARWISE },
340 [VIS_MOVE_SCREEN_LINE_MIDDLE] = { .cur = view_screenline_middle, .type = CHARWISE },
341 [VIS_MOVE_SCREEN_LINE_END] = { .cur = view_screenline_end, .type = CHARWISE|INCLUSIVE },
342 [VIS_MOVE_LINE_PREV] = { .txt = text_line_prev, },
343 [VIS_MOVE_LINE_BEGIN] = { .txt = text_line_begin, },
344 [VIS_MOVE_LINE_START] = { .txt = text_line_start, },
345 [VIS_MOVE_LINE_FINISH] = { .txt = text_line_finish, .type = INCLUSIVE },
346 [VIS_MOVE_LINE_LASTCHAR] = { .txt = text_line_lastchar, .type = INCLUSIVE },
347 [VIS_MOVE_LINE_END] = { .txt = text_line_end, },
348 [VIS_MOVE_LINE_NEXT] = { .txt = text_line_next, },
349 [VIS_MOVE_LINE] = { .vis = line, .type = LINEWISE|IDEMPOTENT|JUMP },
350 [VIS_MOVE_COLUMN] = { .vis = column, .type = CHARWISE|IDEMPOTENT },
351 [VIS_MOVE_CHAR_PREV] = { .txt = text_char_prev, .type = CHARWISE },
352 [VIS_MOVE_CHAR_NEXT] = { .txt = text_char_next, .type = CHARWISE },
353 [VIS_MOVE_LINE_CHAR_PREV] = { .txt = text_line_char_prev, .type = CHARWISE },
354 [VIS_MOVE_LINE_CHAR_NEXT] = { .txt = text_line_char_next, .type = CHARWISE },
355 [VIS_MOVE_WORD_START_PREV] = { .txt = text_word_start_prev, .type = CHARWISE },
356 [VIS_MOVE_WORD_START_NEXT] = { .txt = text_word_start_next, .type = CHARWISE },
357 [VIS_MOVE_WORD_END_PREV] = { .txt = text_word_end_prev, .type = CHARWISE|INCLUSIVE },
358 [VIS_MOVE_WORD_END_NEXT] = { .txt = text_word_end_next, .type = CHARWISE|INCLUSIVE },
359 [VIS_MOVE_LONGWORD_START_PREV] = { .txt = text_longword_start_prev, .type = CHARWISE },
360 [VIS_MOVE_LONGWORD_START_NEXT] = { .txt = text_longword_start_next, .type = CHARWISE },
361 [VIS_MOVE_LONGWORD_END_PREV] = { .txt = text_longword_end_prev, .type = CHARWISE|INCLUSIVE },
362 [VIS_MOVE_LONGWORD_END_NEXT] = { .txt = text_longword_end_next, .type = CHARWISE|INCLUSIVE },
363 [VIS_MOVE_SENTENCE_PREV] = { .txt = text_sentence_prev, .type = CHARWISE },
364 [VIS_MOVE_SENTENCE_NEXT] = { .txt = text_sentence_next, .type = CHARWISE },
365 [VIS_MOVE_PARAGRAPH_PREV] = { .txt = text_paragraph_prev, .type = LINEWISE|JUMP },
366 [VIS_MOVE_PARAGRAPH_NEXT] = { .txt = text_paragraph_next, .type = LINEWISE|JUMP },
367 [VIS_MOVE_FUNCTION_START_PREV] = { .txt = text_function_start_prev, .type = LINEWISE|JUMP },
368 [VIS_MOVE_FUNCTION_START_NEXT] = { .txt = text_function_start_next, .type = LINEWISE|JUMP },
369 [VIS_MOVE_FUNCTION_END_PREV] = { .txt = text_function_end_prev, .type = LINEWISE|JUMP },
370 [VIS_MOVE_FUNCTION_END_NEXT] = { .txt = text_function_end_next, .type = LINEWISE|JUMP },
371 [VIS_MOVE_BLOCK_START] = { .txt = text_block_start, .type = JUMP },
372 [VIS_MOVE_BLOCK_END] = { .txt = text_block_end, .type = JUMP },
373 [VIS_MOVE_PARENTHESE_START] = { .txt = text_parenthese_start, .type = JUMP },
374 [VIS_MOVE_PARENTHESE_END] = { .txt = text_parenthese_end, .type = JUMP },
375 [VIS_MOVE_BRACKET_MATCH] = { .txt = bracket_match, .type = INCLUSIVE|JUMP },
376 [VIS_MOVE_FILE_BEGIN] = { .txt = text_begin, .type = LINEWISE|JUMP },
377 [VIS_MOVE_FILE_END] = { .txt = text_end, .type = LINEWISE|JUMP },
378 [VIS_MOVE_LEFT_TO] = { .vis = to_left, },
379 [VIS_MOVE_RIGHT_TO] = { .vis = to, .type = INCLUSIVE },
380 [VIS_MOVE_LEFT_TILL] = { .vis = till_left, },
381 [VIS_MOVE_RIGHT_TILL] = { .vis = till, .type = INCLUSIVE },
382 [VIS_MOVE_MARK] = { .file = mark_goto, .type = JUMP|IDEMPOTENT },
383 [VIS_MOVE_MARK_LINE] = { .file = mark_line_goto, .type = LINEWISE|JUMP|IDEMPOTENT },
384 [VIS_MOVE_SEARCH_WORD_FORWARD] = { .vis = search_word_forward, .type = JUMP },
385 [VIS_MOVE_SEARCH_WORD_BACKWARD]= { .vis = search_word_backward, .type = JUMP },
386 [VIS_MOVE_SEARCH_NEXT] = { .vis = search_forward, .type = JUMP },
387 [VIS_MOVE_SEARCH_PREV] = { .vis = search_backward, .type = JUMP },
388 [VIS_MOVE_WINDOW_LINE_TOP] = { .view = view_lines_top, .type = LINEWISE|JUMP|IDEMPOTENT },
389 [VIS_MOVE_WINDOW_LINE_MIDDLE] = { .view = view_lines_middle, .type = LINEWISE|JUMP|IDEMPOTENT },
390 [VIS_MOVE_WINDOW_LINE_BOTTOM] = { .view = view_lines_bottom, .type = LINEWISE|JUMP|IDEMPOTENT },
391 [VIS_MOVE_CHANGELIST_NEXT] = { .win = window_changelist_next, .type = INCLUSIVE },
392 [VIS_MOVE_CHANGELIST_PREV] = { .win = window_changelist_prev, .type = INCLUSIVE },
393 [VIS_MOVE_JUMPLIST_NEXT] = { .win = window_jumplist_next, .type = INCLUSIVE },
394 [VIS_MOVE_JUMPLIST_PREV] = { .win = window_jumplist_prev, .type = INCLUSIVE },
395 [VIS_MOVE_NOP] = { .win = window_nop, .type = IDEMPOTENT },
396 [VIS_MOVE_PERCENT] = { .vis = percent, .type = IDEMPOTENT },