Overhaul build system
[vis.git] / text-motions.c
blobcde74cc29834bf47ea813da9d3f130074c824c83
1 /*
2 * Copyright (c) 2014 Marc André Tanner <mat at brain-dump.org>
4 * Permission to use, copy, modify, and/or distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 #include <ctype.h>
17 #include <string.h>
18 #include "text-motions.h"
19 #include "text-util.h"
20 #include "util.h"
22 // TODO: specify this per file type?
23 int is_word_boundry(int c) {
24 return ISASCII(c) && !(('0' <= c && c <= '9') ||
25 ('a' <= c && c <= 'z') ||
26 ('A' <= c && c <= 'Z') || c == '_');
29 size_t text_begin(Text *txt, size_t pos) {
30 return 0;
33 size_t text_end(Text *txt, size_t pos) {
34 return text_size(txt);
37 size_t text_char_next(Text *txt, size_t pos) {
38 Iterator it = text_iterator_get(txt, pos);
39 text_iterator_char_next(&it, NULL);
40 return it.pos;
43 size_t text_char_prev(Text *txt, size_t pos) {
44 Iterator it = text_iterator_get(txt, pos);
45 text_iterator_char_prev(&it, NULL);
46 return it.pos;
49 static size_t find_next(Text *txt, size_t pos, const char *s, bool line) {
50 if (!s)
51 return pos;
52 size_t len = strlen(s), matched = 0;
53 Iterator it = text_iterator_get(txt, pos), sit;
54 for (char c; matched < len && text_iterator_byte_get(&it, &c); ) {
55 if (c == s[matched]) {
56 if (matched == 0)
57 sit = it;
58 matched++;
59 } else if (matched > 0) {
60 it = sit;
61 matched = 0;
63 text_iterator_byte_next(&it, NULL);
64 if (line && c == '\n')
65 break;
67 return matched == len ? it.pos - len : pos;
70 size_t text_find_next(Text *txt, size_t pos, const char *s) {
71 return find_next(txt, pos, s, false);
74 size_t text_line_find_next(Text *txt, size_t pos, const char *s) {
75 return find_next(txt, pos, s, true);
78 static size_t find_prev(Text *txt, size_t pos, const char *s, bool line) {
79 if (!s)
80 return pos;
81 size_t len = strlen(s), matched = len - 1;
82 Iterator it = text_iterator_get(txt, pos), sit;
83 if (len == 0)
84 return pos;
85 for (char c; text_iterator_byte_get(&it, &c); ) {
86 if (c == s[matched]) {
87 if (matched == 0)
88 break;
89 if (matched == len - 1)
90 sit = it;
91 matched--;
92 } else if (matched < len - 1) {
93 it = sit;
94 matched = len - 1;
96 text_iterator_byte_prev(&it, NULL);
97 if (line && c == '\n')
98 break;
100 return matched == 0 ? it.pos : pos;
103 size_t text_find_prev(Text *txt, size_t pos, const char *s) {
104 return find_prev(txt, pos, s, false);
107 size_t text_line_find_prev(Text *txt, size_t pos, const char *s) {
108 return find_prev(txt, pos, s, true);
111 size_t text_line_prev(Text *txt, size_t pos) {
112 char c;
113 Iterator it = text_iterator_get(txt, pos);
114 if (!text_iterator_byte_get(&it, &c))
115 return pos;
116 if (c == '\n')
117 text_iterator_byte_prev(&it, &c);
118 if (c == '\r')
119 text_iterator_byte_prev(&it, &c);
120 while (text_iterator_byte_get(&it, &c) && c != '\n')
121 text_iterator_byte_prev(&it, NULL);
122 if (text_iterator_byte_prev(&it, &c) && c != '\r')
123 text_iterator_byte_next(&it, &c);
124 return it.pos;
127 size_t text_line_begin(Text *txt, size_t pos) {
128 char c;
129 Iterator it = text_iterator_get(txt, pos);
130 if (!text_iterator_byte_get(&it, &c))
131 return pos;
132 if (c == '\n')
133 text_iterator_byte_prev(&it, &c);
134 if (c == '\r')
135 text_iterator_byte_prev(&it, &c);
136 while (text_iterator_byte_get(&it, &c)) {
137 if (c == '\n') {
138 it.pos++;
139 break;
141 text_iterator_byte_prev(&it, NULL);
143 return it.pos;
146 size_t text_line_start(Text *txt, size_t pos) {
147 char c;
148 Iterator it = text_iterator_get(txt, text_line_begin(txt, pos));
149 while (text_iterator_byte_get(&it, &c) && c != '\n' && isspace(c))
150 text_iterator_byte_next(&it, NULL);
151 return it.pos;
154 size_t text_line_finish(Text *txt, size_t pos) {
155 char c;
156 Iterator it = text_iterator_get(txt, text_line_end(txt, pos));
157 do text_iterator_char_prev(&it, NULL);
158 while (text_iterator_byte_get(&it, &c) && c != '\n' && isspace(c));
159 return it.pos;
162 size_t text_line_lastchar(Text *txt, size_t pos) {
163 char c;
164 Iterator it = text_iterator_get(txt, text_line_end(txt, pos));
165 if (text_iterator_char_prev(&it, &c) && c == '\n')
166 text_iterator_byte_next(&it, NULL);
167 return it.pos;
170 size_t text_line_end(Text *txt, size_t pos) {
171 char c;
172 Iterator it = text_iterator_get(txt, pos);
173 while (text_iterator_byte_get(&it, &c) && c != '\r' && c != '\n')
174 text_iterator_byte_next(&it, NULL);
175 return it.pos;
178 size_t text_line_next(Text *txt, size_t pos) {
179 char c;
180 Iterator it = text_iterator_get(txt, pos);
181 while (text_iterator_byte_get(&it, &c) && c != '\n')
182 text_iterator_byte_next(&it, NULL);
183 text_iterator_byte_next(&it, NULL);
184 return it.pos;
187 size_t text_line_offset(Text *txt, size_t pos, size_t off) {
188 char c;
189 size_t bol = text_line_begin(txt, pos);
190 Iterator it = text_iterator_get(txt, bol);
191 while (off-- > 0 && text_iterator_byte_get(&it, &c) && c != '\r' && c != '\n')
192 text_iterator_byte_next(&it, NULL);
193 return it.pos;
196 size_t text_line_char_set(Text *txt, size_t pos, int count) {
197 char c;
198 size_t bol = text_line_begin(txt, pos);
199 Iterator it = text_iterator_get(txt, bol);
200 while (count-- > 0 && text_iterator_byte_get(&it, &c) && c != '\r' && c != '\n')
201 text_iterator_char_next(&it, NULL);
202 return it.pos;
205 int text_line_char_get(Text *txt, size_t pos) {
206 char c;
207 int count = 0;
208 size_t bol = text_line_begin(txt, pos);
209 Iterator it = text_iterator_get(txt, bol);
210 while (text_iterator_byte_get(&it, &c) && it.pos < pos && c != '\r' && c != '\n') {
211 text_iterator_char_next(&it, NULL);
212 count++;
214 return count;
217 size_t text_line_char_next(Text *txt, size_t pos) {
218 char c;
219 Iterator it = text_iterator_get(txt, pos);
220 if (!text_iterator_byte_get(&it, &c) || c == '\r' || c == '\n')
221 return pos;
222 if (!text_iterator_char_next(&it, &c) || c == '\r' || c == '\n')
223 return pos;
224 return it.pos;
227 size_t text_line_char_prev(Text *txt, size_t pos) {
228 char c;
229 Iterator it = text_iterator_get(txt, pos);
230 if (!text_iterator_char_prev(&it, &c) || c == '\n')
231 return pos;
232 return it.pos;
235 size_t text_line_up(Text *txt, size_t pos) {
236 int count = text_line_char_get(txt, pos);
237 size_t prev = text_line_prev(txt, pos);
238 return text_line_char_set(txt, prev, count);
241 size_t text_line_down(Text *txt, size_t pos) {
242 int count = text_line_char_get(txt, pos);
243 size_t next = text_line_next(txt, pos);
244 return text_line_char_set(txt, next, count);
247 size_t text_range_line_first(Text *txt, Filerange *r) {
248 if (!text_range_valid(r))
249 return EPOS;
250 return r->start;
253 size_t text_range_line_last(Text *txt, Filerange *r) {
254 if (!text_range_valid(r))
255 return EPOS;
256 size_t pos = text_line_begin(txt, r->end);
257 if (pos == r->end) {
258 /* range ends at a begin of a line, skip last line ending */
259 pos = text_line_prev(txt, pos);
260 pos = text_line_begin(txt, pos);
262 return r->start <= pos ? pos : r->start;
265 size_t text_range_line_next(Text *txt, Filerange *r, size_t pos) {
266 if (!text_range_contains(r, pos))
267 return EPOS;
268 size_t newpos = text_line_next(txt, pos);
269 return newpos != pos && newpos < r->end ? newpos : EPOS;
272 size_t text_range_line_prev(Text *txt, Filerange *r, size_t pos) {
273 if (!text_range_contains(r, pos))
274 return EPOS;
275 size_t newpos = text_line_begin(txt, text_line_prev(txt, pos));
276 return newpos != pos && r->start <= newpos ? newpos : EPOS;
279 static size_t text_customword_start_next(Text *txt, size_t pos, int (*isboundry)(int)) {
280 char c;
281 Iterator it = text_iterator_get(txt, pos);
282 if (!text_iterator_byte_get(&it, &c))
283 return pos;
284 if (isboundry(c))
285 while (isboundry(c) && !isspace(c) && text_iterator_char_next(&it, &c));
286 else
287 while (!isboundry(c) && text_iterator_char_next(&it, &c));
288 while (isspace(c) && text_iterator_char_next(&it, &c));
289 return it.pos;
292 static size_t text_customword_start_prev(Text *txt, size_t pos, int (*isboundry)(int)) {
293 char c;
294 Iterator it = text_iterator_get(txt, pos);
295 while (text_iterator_char_prev(&it, &c) && isspace(c));
296 if (isboundry(c))
297 do pos = it.pos; while (text_iterator_char_prev(&it, &c) && isboundry(c) && !isspace(c));
298 else
299 do pos = it.pos; while (text_iterator_char_prev(&it, &c) && !isboundry(c));
300 return pos;
303 static size_t text_customword_end_next(Text *txt, size_t pos, int (*isboundry)(int)) {
304 char c;
305 Iterator it = text_iterator_get(txt, pos);
306 while (text_iterator_char_next(&it, &c) && isspace(c));
307 if (isboundry(c))
308 do pos = it.pos; while (text_iterator_char_next(&it, &c) && isboundry(c) && !isspace(c));
309 else
310 do pos = it.pos; while (text_iterator_char_next(&it, &c) && !isboundry(c));
311 return pos;
314 static size_t text_customword_end_prev(Text *txt, size_t pos, int (*isboundry)(int)) {
315 char c;
316 Iterator it = text_iterator_get(txt, pos);
317 if (!text_iterator_byte_get(&it, &c))
318 return pos;
319 if (isboundry(c))
320 while (isboundry(c) && !isspace(c) && text_iterator_char_prev(&it, &c));
321 else
322 while (!isboundry(c) && text_iterator_char_prev(&it, &c));
323 while (isspace(c) && text_iterator_char_prev(&it, &c));
324 return it.pos;
327 size_t text_longword_end_next(Text *txt, size_t pos) {
328 return text_customword_end_next(txt, pos, isspace);
331 size_t text_longword_end_prev(Text *txt, size_t pos) {
332 return text_customword_end_prev(txt, pos, isspace);
335 size_t text_longword_start_next(Text *txt, size_t pos) {
336 return text_customword_start_next(txt, pos, isspace);
339 size_t text_longword_start_prev(Text *txt, size_t pos) {
340 return text_customword_start_prev(txt, pos, isspace);
343 size_t text_word_end_next(Text *txt, size_t pos) {
344 return text_customword_end_next(txt, pos, is_word_boundry);
347 size_t text_word_end_prev(Text *txt, size_t pos) {
348 return text_customword_end_prev(txt, pos, is_word_boundry);
351 size_t text_word_start_next(Text *txt, size_t pos) {
352 return text_customword_start_next(txt, pos, is_word_boundry);
355 size_t text_word_start_prev(Text *txt, size_t pos) {
356 return text_customword_start_prev(txt, pos, is_word_boundry);
359 static size_t text_paragraph_sentence_next(Text *txt, size_t pos, bool sentence) {
360 char c;
361 bool content = false, paragraph = false;
362 Iterator it = text_iterator_get(txt, pos);
363 while (text_iterator_byte_next(&it, &c)) {
364 content |= !isspace(c);
365 if (sentence && (c == '.' || c == '?' || c == '!') && text_iterator_byte_next(&it, &c) && isspace(c)) {
366 if (c == '\n' && text_iterator_byte_next(&it, &c)) {
367 if (c == '\r')
368 text_iterator_byte_next(&it, &c);
369 } else {
370 while (text_iterator_byte_get(&it, &c) && c == ' ')
371 text_iterator_byte_next(&it, NULL);
373 break;
375 if (c == '\n' && text_iterator_byte_next(&it, &c)) {
376 if (c == '\r')
377 text_iterator_byte_next(&it, &c);
378 content |= !isspace(c);
379 if (c == '\n')
380 paragraph = true;
382 if (content && paragraph)
383 break;
385 return it.pos;
388 static size_t text_paragraph_sentence_prev(Text *txt, size_t pos, bool sentence) {
389 char prev, c;
390 bool content = false, paragraph = false;
392 Iterator it = text_iterator_get(txt, pos);
393 if (!text_iterator_byte_get(&it, &prev))
394 return pos;
396 while (text_iterator_byte_prev(&it, &c)) {
397 content |= !isspace(c) && c != '.' && c != '?' && c != '!';
398 if (sentence && content && (c == '.' || c == '?' || c == '!') && isspace(prev)) {
399 do text_iterator_byte_next(&it, NULL);
400 while (text_iterator_byte_get(&it, &c) && isspace(c));
401 break;
403 if (c == '\r')
404 text_iterator_byte_prev(&it, &c);
405 if (c == '\n' && text_iterator_byte_prev(&it, &c)) {
406 content |= !isspace(c);
407 if (c == '\r')
408 text_iterator_byte_prev(&it, &c);
409 if (c == '\n') {
410 paragraph = true;
411 if (content) {
412 do text_iterator_byte_next(&it, NULL);
413 while (text_iterator_byte_get(&it, &c) && isspace(c));
414 break;
418 if (content && paragraph) {
419 do text_iterator_byte_next(&it, NULL);
420 while (text_iterator_byte_get(&it, &c) && !isspace(c));
421 break;
423 prev = c;
425 return it.pos;
428 size_t text_sentence_next(Text *txt, size_t pos) {
429 return text_paragraph_sentence_next(txt, pos, true);
432 size_t text_sentence_prev(Text *txt, size_t pos) {
433 return text_paragraph_sentence_prev(txt, pos, true);
436 size_t text_paragraph_next(Text *txt, size_t pos) {
437 return text_paragraph_sentence_next(txt, pos, false);
440 size_t text_paragraph_prev(Text *txt, size_t pos) {
441 return text_paragraph_sentence_prev(txt, pos, false);
444 size_t text_line_empty_next(Text *txt, size_t pos) {
445 // TODO refactor search \n\n
446 char c;
447 Iterator it = text_iterator_get(txt, pos);
448 while (text_iterator_byte_get(&it, &c)) {
449 if (c == '\n' && text_iterator_byte_next(&it, &c)) {
450 size_t match = it.pos;
451 if (c == '\r')
452 text_iterator_byte_next(&it, &c);
453 if (c == '\n')
454 return match;
456 text_iterator_byte_next(&it, NULL);
458 return pos;
461 size_t text_line_empty_prev(Text *txt, size_t pos) {
462 // TODO refactor search \n\n
463 char c;
464 Iterator it = text_iterator_get(txt, pos);
465 while (text_iterator_byte_prev(&it, &c)) {
466 if (c == '\n' && text_iterator_byte_prev(&it, &c)) {
467 if (c == '\r')
468 text_iterator_byte_prev(&it, &c);
469 if (c == '\n')
470 return it.pos + 1;
473 return pos;
476 size_t text_function_start_next(Text *txt, size_t pos) {
477 size_t a = text_function_end_next(txt, pos);
478 size_t b = a;
479 char c;
480 if (a != pos) {
481 Iterator it = text_iterator_get(txt, a);
482 while (text_iterator_byte_next(&it, &c) && (c == '\r' || c == '\n'));
483 a = it.pos;
485 if (b != pos) {
486 size_t match = text_bracket_match(txt, b);
487 b = match != b ? text_line_next(txt, text_line_empty_prev(txt, match)) : pos;
489 if (a <= pos && b <= pos)
490 return pos;
491 else if (a <= pos)
492 return b;
493 else if (b <= pos)
494 return a;
495 else
496 return MIN(a, b);
499 size_t text_function_start_prev(Text *txt, size_t pos) {
500 char c;
501 size_t apos = text_byte_get(txt, pos, &c) && c == '}' && pos > 0 ? pos - 1 : pos;
502 size_t a = text_function_end_next(txt, apos);
503 size_t b = text_function_end_prev(txt, pos);
504 if (a != apos) {
505 size_t match = text_bracket_match(txt, a);
506 a = match != a ? text_line_next(txt, text_line_empty_prev(txt, match)) : pos;
508 if (b != pos) {
509 size_t match = text_bracket_match(txt, b);
510 b = match != b ? text_line_next(txt, text_line_empty_prev(txt, match)) : pos;
512 if (a >= pos && b >= pos)
513 return pos;
514 else if (a >= pos)
515 return b;
516 else if (b >= pos)
517 return a;
518 else
519 return MAX(a, b);
522 static size_t text_function_end_direction(Text *txt, size_t pos, int direction) {
523 size_t start = pos, match;
524 if (direction < 0 && pos > 0)
525 pos--;
526 for (;;) {
527 char c[3];
528 if (direction > 0)
529 match = text_find_next(txt, pos, "\n}");
530 else
531 match = text_find_prev(txt, pos, "\n}");
532 if (text_bytes_get(txt, match, sizeof c, c) != 3 || c[0] != '\n' || c[1] != '}')
533 break;
534 if (c[2] == '\r' || c[2] == '\n')
535 return match+1;
536 if (match == pos)
537 match += direction;
538 pos = match;
540 return start;
543 size_t text_function_end_next(Text *txt, size_t pos) {
544 return text_function_end_direction(txt, pos, +1);
547 size_t text_function_end_prev(Text *txt, size_t pos) {
548 return text_function_end_direction(txt, pos, -1);
551 size_t text_bracket_match(Text *txt, size_t pos) {
552 return text_bracket_match_except(txt, pos, NULL);
555 size_t text_bracket_match_except(Text *txt, size_t pos, const char *except) {
556 int direction, count = 1;
557 char search, current, c;
558 bool instring = false;
559 Iterator it = text_iterator_get(txt, pos);
560 if (!text_iterator_byte_get(&it, &current))
561 return pos;
562 if (except && memchr(except, current, strlen(except)))
563 return pos;
564 switch (current) {
565 case '(': search = ')'; direction = 1; break;
566 case ')': search = '('; direction = -1; break;
567 case '{': search = '}'; direction = 1; break;
568 case '}': search = '{'; direction = -1; break;
569 case '[': search = ']'; direction = 1; break;
570 case ']': search = '['; direction = -1; break;
571 case '<': search = '>'; direction = 1; break;
572 case '>': search = '<'; direction = -1; break;
573 case '"':
574 case '`':
575 case '\'': {
576 char special[] = " \n)}]>.,:;";
577 search = current;
578 direction = 1;
579 if (text_iterator_byte_next(&it, &c)) {
580 /* if a single or double quote is followed by
581 * a special character, search backwards */
582 if (memchr(special, c, sizeof(special)))
583 direction = -1;
584 text_iterator_byte_prev(&it, NULL);
586 break;
588 default: return pos;
591 if (direction >= 0) { /* forward search */
592 while (text_iterator_byte_next(&it, &c)) {
593 if (c != current && c == '"')
594 instring = !instring;
595 if (!instring) {
596 if (c == search && --count == 0)
597 return it.pos;
598 else if (c == current)
599 count++;
602 } else { /* backwards */
603 while (text_iterator_byte_prev(&it, &c)) {
604 if (c != current && c == '"')
605 instring = !instring;
606 if (!instring) {
607 if (c == search && --count == 0)
608 return it.pos;
609 else if (c == current)
610 count++;
615 return pos; /* no match found */
618 size_t text_search_forward(Text *txt, size_t pos, Regex *regex) {
619 int start = pos + 1;
620 int end = text_size(txt);
621 RegexMatch match[1];
622 bool found = !text_search_range_forward(txt, start, end - start, regex, 1, match, 0);
624 if (!found) {
625 start = 0;
626 end = pos;
627 found = !text_search_range_forward(txt, start, end, regex, 1, match, 0);
630 return found ? match[0].start : pos;
633 size_t text_search_backward(Text *txt, size_t pos, Regex *regex) {
634 int start = 0;
635 int end = pos;
636 RegexMatch match[1];
637 bool found = !text_search_range_backward(txt, start, end, regex, 1, match, 0);
639 if (!found) {
640 start = pos + 1;
641 end = text_size(txt);
642 found = !text_search_range_backward(txt, start, end - start, regex, 1, match, 0);
645 return found ? match[0].start : pos;