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.
18 #include "text-motions.h"
19 #include "text-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
) {
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
);
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
);
49 static size_t find_next(Text
*txt
, size_t pos
, const char *s
, bool line
) {
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
]) {
59 } else if (matched
> 0) {
63 text_iterator_byte_next(&it
, NULL
);
64 if (line
&& c
== '\n')
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
) {
81 size_t len
= strlen(s
), matched
= len
- 1;
82 Iterator it
= text_iterator_get(txt
, pos
), sit
;
85 for (char c
; text_iterator_byte_get(&it
, &c
); ) {
86 if (c
== s
[matched
]) {
89 if (matched
== len
- 1)
92 } else if (matched
< len
- 1) {
96 text_iterator_byte_prev(&it
, NULL
);
97 if (line
&& c
== '\n')
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
) {
113 Iterator it
= text_iterator_get(txt
, pos
);
114 if (!text_iterator_byte_get(&it
, &c
))
117 text_iterator_byte_prev(&it
, &c
);
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
);
127 size_t text_line_begin(Text
*txt
, size_t pos
) {
129 Iterator it
= text_iterator_get(txt
, pos
);
130 if (!text_iterator_byte_get(&it
, &c
))
133 text_iterator_byte_prev(&it
, &c
);
135 text_iterator_byte_prev(&it
, &c
);
136 while (text_iterator_byte_get(&it
, &c
)) {
141 text_iterator_byte_prev(&it
, NULL
);
146 size_t text_line_start(Text
*txt
, size_t pos
) {
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
);
154 size_t text_line_finish(Text
*txt
, size_t pos
) {
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
));
162 size_t text_line_lastchar(Text
*txt
, size_t pos
) {
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
);
170 size_t text_line_end(Text
*txt
, size_t pos
) {
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
);
178 size_t text_line_next(Text
*txt
, size_t pos
) {
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
);
187 size_t text_line_offset(Text
*txt
, size_t pos
, size_t off
) {
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
);
196 size_t text_line_char_set(Text
*txt
, size_t pos
, int count
) {
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
);
205 int text_line_char_get(Text
*txt
, size_t pos
) {
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
);
217 size_t text_line_char_next(Text
*txt
, size_t pos
) {
219 Iterator it
= text_iterator_get(txt
, pos
);
220 if (!text_iterator_byte_get(&it
, &c
) || c
== '\r' || c
== '\n')
222 if (!text_iterator_char_next(&it
, &c
) || c
== '\r' || c
== '\n')
227 size_t text_line_char_prev(Text
*txt
, size_t pos
) {
229 Iterator it
= text_iterator_get(txt
, pos
);
230 if (!text_iterator_char_prev(&it
, &c
) || c
== '\n')
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
))
253 size_t text_range_line_last(Text
*txt
, Filerange
*r
) {
254 if (!text_range_valid(r
))
256 size_t pos
= text_line_begin(txt
, 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
))
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
))
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)) {
281 Iterator it
= text_iterator_get(txt
, pos
);
282 if (!text_iterator_byte_get(&it
, &c
))
285 while (isboundry(c
) && !isspace(c
) && text_iterator_char_next(&it
, &c
));
287 while (!isboundry(c
) && text_iterator_char_next(&it
, &c
));
288 while (isspace(c
) && text_iterator_char_next(&it
, &c
));
292 static size_t text_customword_start_prev(Text
*txt
, size_t pos
, int (*isboundry
)(int)) {
294 Iterator it
= text_iterator_get(txt
, pos
);
295 while (text_iterator_char_prev(&it
, &c
) && isspace(c
));
297 do pos
= it
.pos
; while (text_iterator_char_prev(&it
, &c
) && isboundry(c
) && !isspace(c
));
299 do pos
= it
.pos
; while (text_iterator_char_prev(&it
, &c
) && !isboundry(c
));
303 static size_t text_customword_end_next(Text
*txt
, size_t pos
, int (*isboundry
)(int)) {
305 Iterator it
= text_iterator_get(txt
, pos
);
306 while (text_iterator_char_next(&it
, &c
) && isspace(c
));
308 do pos
= it
.pos
; while (text_iterator_char_next(&it
, &c
) && isboundry(c
) && !isspace(c
));
310 do pos
= it
.pos
; while (text_iterator_char_next(&it
, &c
) && !isboundry(c
));
314 static size_t text_customword_end_prev(Text
*txt
, size_t pos
, int (*isboundry
)(int)) {
316 Iterator it
= text_iterator_get(txt
, pos
);
317 if (!text_iterator_byte_get(&it
, &c
))
320 while (isboundry(c
) && !isspace(c
) && text_iterator_char_prev(&it
, &c
));
322 while (!isboundry(c
) && text_iterator_char_prev(&it
, &c
));
323 while (isspace(c
) && text_iterator_char_prev(&it
, &c
));
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
) {
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
)) {
368 text_iterator_byte_next(&it
, &c
);
370 while (text_iterator_byte_get(&it
, &c
) && c
== ' ')
371 text_iterator_byte_next(&it
, NULL
);
375 if (c
== '\n' && text_iterator_byte_next(&it
, &c
)) {
377 text_iterator_byte_next(&it
, &c
);
378 content
|= !isspace(c
);
382 if (content
&& paragraph
)
388 static size_t text_paragraph_sentence_prev(Text
*txt
, size_t pos
, bool sentence
) {
390 bool content
= false, paragraph
= false;
392 Iterator it
= text_iterator_get(txt
, pos
);
393 if (!text_iterator_byte_get(&it
, &prev
))
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
));
404 text_iterator_byte_prev(&it
, &c
);
405 if (c
== '\n' && text_iterator_byte_prev(&it
, &c
)) {
406 content
|= !isspace(c
);
408 text_iterator_byte_prev(&it
, &c
);
412 do text_iterator_byte_next(&it
, NULL
);
413 while (text_iterator_byte_get(&it
, &c
) && isspace(c
));
418 if (content
&& paragraph
) {
419 do text_iterator_byte_next(&it
, NULL
);
420 while (text_iterator_byte_get(&it
, &c
) && !isspace(c
));
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
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
;
452 text_iterator_byte_next(&it
, &c
);
456 text_iterator_byte_next(&it
, NULL
);
461 size_t text_line_empty_prev(Text
*txt
, size_t pos
) {
462 // TODO refactor search \n\n
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
)) {
468 text_iterator_byte_prev(&it
, &c
);
476 size_t text_function_start_next(Text
*txt
, size_t pos
) {
477 size_t a
= text_function_end_next(txt
, pos
);
481 Iterator it
= text_iterator_get(txt
, a
);
482 while (text_iterator_byte_next(&it
, &c
) && (c
== '\r' || c
== '\n'));
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
)
499 size_t text_function_start_prev(Text
*txt
, size_t pos
) {
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
);
505 size_t match
= text_bracket_match(txt
, a
);
506 a
= match
!= a
? text_line_next(txt
, text_line_empty_prev(txt
, match
)) : 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
)
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)
529 match
= text_find_next(txt
, pos
, "\n}");
531 match
= text_find_prev(txt
, pos
, "\n}");
532 if (text_bytes_get(txt
, match
, sizeof c
, c
) != 3 || c
[0] != '\n' || c
[1] != '}')
534 if (c
[2] == '\r' || c
[2] == '\n')
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
, ¤t
))
562 if (except
&& memchr(except
, current
, strlen(except
)))
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;
576 char special
[] = " \n)}]>.,:;";
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
)))
584 text_iterator_byte_prev(&it
, NULL
);
591 if (direction
>= 0) { /* forward search */
592 while (text_iterator_byte_next(&it
, &c
)) {
593 if (c
!= current
&& c
== '"')
594 instring
= !instring
;
596 if (c
== search
&& --count
== 0)
598 else if (c
== current
)
602 } else { /* backwards */
603 while (text_iterator_byte_prev(&it
, &c
)) {
604 if (c
!= current
&& c
== '"')
605 instring
= !instring
;
607 if (c
== search
&& --count
== 0)
609 else if (c
== current
)
615 return pos
; /* no match found */
618 size_t text_search_forward(Text
*txt
, size_t pos
, Regex
*regex
) {
620 int end
= text_size(txt
);
622 bool found
= !text_search_range_forward(txt
, start
, end
- start
, regex
, 1, match
, 0);
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
) {
637 bool found
= !text_search_range_backward(txt
, start
, end
, regex
, 1, match
, 0);
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
;