ui: fix line number drawing
[vis.git] / text-objects.c
blobf4fcb4a9cfeffef5a0d6381ddc7d3012612f39ad
1 #include <errno.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <ctype.h>
5 #include "text-motions.h"
6 #include "text-objects.h"
7 #include "text-util.h"
8 #include "util.h"
10 #define blank(c) ((c) == ' ' || (c) == '\t')
11 #define space(c) (isspace((unsigned char)c))
12 #define boundary(c) (isboundary((unsigned char)c))
14 Filerange text_object_entire(Text *txt, size_t pos) {
15 return text_range_new(0, text_size(txt));
18 Filerange text_object_entire_inner(Text *txt, size_t pos) {
19 char c;
20 Filerange r = text_object_entire(txt, pos);
21 Iterator it = text_iterator_get(txt, r.start);
22 if (text_iterator_byte_get(&it, &c) && c == '\n')
23 while (text_iterator_byte_next(&it, &c) && c == '\n');
24 r.start = it.pos;
25 it = text_iterator_get(txt, r.end);
26 while (text_iterator_char_prev(&it, &c) && c == '\n');
27 r.end = it.pos;
28 return text_range_linewise(txt, &r);
31 static Filerange text_object_customword(Text *txt, size_t pos, int (*isboundary)(int)) {
32 Filerange r;
33 char c, prev = '0', next = '0';
34 Iterator it = text_iterator_get(txt, pos);
35 if (!text_iterator_byte_get(&it, &c))
36 return text_range_empty();
37 if (pos > 0 && text_iterator_byte_prev(&it, &prev))
38 text_iterator_byte_next(&it, NULL);
39 text_iterator_byte_next(&it, &next);
40 if (space(c)) {
41 r.start = text_char_next(txt, text_customword_end_prev(txt, pos, isboundary));
42 r.end = text_customword_start_next(txt, pos, isboundary);
43 } else if (boundary(prev) && boundary(next)) {
44 if (boundary(c)) {
45 r.start = text_char_next(txt, text_customword_end_prev(txt, pos, isboundary));
46 r.end = text_char_next(txt, text_customword_end_next(txt, pos, isboundary));
47 } else {
48 /* on a single character */
49 r.start = pos;
50 r.end = text_char_next(txt, pos);
52 } else if (boundary(prev)) {
53 /* at start of a word */
54 r.start = pos;
55 r.end = text_char_next(txt, text_customword_end_next(txt, pos, isboundary));
56 } else if (boundary(next)) {
57 /* at end of a word */
58 r.start = text_customword_start_prev(txt, pos, isboundary);
59 r.end = text_char_next(txt, pos);
60 } else {
61 /* in the middle of a word */
62 r.start = text_customword_start_prev(txt, pos, isboundary);
63 r.end = text_char_next(txt, text_customword_end_next(txt, pos, isboundary));
66 return r;
69 Filerange text_object_word(Text *txt, size_t pos) {
70 return text_object_customword(txt, pos, is_word_boundary);
73 Filerange text_object_longword(Text *txt, size_t pos) {
74 return text_object_customword(txt, pos, isspace);
77 static Filerange text_object_customword_outer(Text *txt, size_t pos, int (*isboundary)(int)) {
78 Filerange r;
79 char c, prev = '0', next = '0';
80 Iterator it = text_iterator_get(txt, pos);
81 if (!text_iterator_byte_get(&it, &c))
82 return text_range_empty();
83 if (pos > 0 && text_iterator_byte_prev(&it, &prev))
84 text_iterator_byte_next(&it, NULL);
85 text_iterator_byte_next(&it, &next);
86 if (space(c)) {
87 /* middle of two words, include leading white space */
88 r.start = text_char_next(txt, text_customword_end_prev(txt, pos, isboundary));
89 r.end = text_char_next(txt, text_customword_end_next(txt, pos, isboundary));
90 } else if (boundary(prev) && boundary(next)) {
91 if (boundary(c)) {
92 r.start = text_char_next(txt, text_customword_end_prev(txt, pos, isboundary));
93 r.end = text_word_start_next(txt, text_customword_end_next(txt, pos, isboundary));
94 } else {
95 /* on a single character */
96 r.start = pos;
97 r.end = text_customword_start_next(txt, pos, isboundary);
99 } else if (boundary(prev)) {
100 /* at start of a word */
101 r.start = pos;
102 r.end = text_customword_start_next(txt, text_customword_end_next(txt, pos, isboundary), isboundary);
103 } else if (boundary(next)) {
104 /* at end of a word */
105 r.start = text_customword_start_prev(txt, pos, isboundary);
106 r.end = text_customword_start_next(txt, pos, isboundary);
107 } else {
108 /* in the middle of a word */
109 r.start = text_customword_start_prev(txt, pos, isboundary);
110 r.end = text_customword_start_next(txt, text_customword_end_next(txt, pos, isboundary), isboundary);
113 return r;
116 Filerange text_object_longword_outer(Text *txt, size_t pos) {
117 return text_object_customword_outer(txt, pos, isspace);
120 Filerange text_object_word_outer(Text *txt, size_t pos) {
121 return text_object_customword_outer(txt, pos, is_word_boundary);
124 Filerange text_object_word_find_next(Text *txt, size_t pos, const char *word) {
125 size_t len = strlen(word);
126 for (;;) {
127 size_t match_pos = text_find_next(txt, pos, word);
128 if (match_pos != pos) {
129 Filerange match_word = text_object_word(txt, match_pos);
130 if (text_range_size(&match_word) == len)
131 return match_word;
132 pos = match_word.end;
133 } else {
134 return text_range_empty();
139 Filerange text_object_word_find_prev(Text *txt, size_t pos, const char *word) {
140 size_t len = strlen(word);
141 for (;;) {
142 size_t match_pos = text_find_prev(txt, pos, word);
143 if (match_pos != pos) {
144 Filerange match_word = text_object_word(txt, match_pos);
145 if (text_range_size(&match_word) == len)
146 return match_word;
147 pos = match_pos;
148 } else {
149 return text_range_empty();
154 Filerange text_object_line(Text *txt, size_t pos) {
155 Filerange r;
156 r.start = text_line_begin(txt, pos);
157 r.end = text_line_next(txt, pos);
158 return r;
161 Filerange text_object_line_inner(Text *txt, size_t pos) {
162 Filerange r = text_object_line(txt, pos);
163 return text_range_inner(txt, &r);
166 Filerange text_object_sentence(Text *txt, size_t pos) {
167 Filerange r;
168 r.start = text_sentence_prev(txt, pos);
169 r.end = text_sentence_next(txt, pos);
170 return r;
173 static bool text_line_blank(Text *txt, size_t pos) {
174 char c;
175 bool b = true;
176 Iterator it = text_iterator_get(txt, text_line_begin(txt, pos));
177 while (text_iterator_byte_get(&it, &c) && c != '\n' && (b = blank(c)))
178 text_iterator_char_next(&it, NULL);
179 return b;
182 Filerange text_object_paragraph(Text *txt, size_t pos) {
183 char c;
184 Filerange r;
185 if (text_line_blank(txt, pos)) {
186 Iterator it = text_iterator_get(txt, pos), rit = it;
187 while (text_iterator_byte_get(&rit, &c) && (c == '\n' || blank(c)))
188 text_iterator_byte_prev(&rit, NULL);
189 if (c == '\n' || blank(c))
190 r.start = rit.pos;
191 else
192 r.start = text_line_next(txt, rit.pos);
193 while (text_iterator_byte_get(&it, &c) && (c == '\n' || blank(c)))
194 text_iterator_byte_next(&it, NULL);
195 if (it.pos == text_size(txt))
196 r.end = rit.pos;
197 else
198 r.end = text_line_begin(txt, it.pos);
199 } else {
200 r.start = text_line_blank_prev(txt, pos);
201 if (r.start > 0 || (text_byte_get(txt, r.start, &c) && c == '\n'))
202 r.start = text_line_next(txt, r.start);
203 r.end = text_line_blank_next(txt, pos);
205 return r;
208 Filerange text_object_paragraph_outer(Text *txt, size_t pos) {
209 Filerange p1 = text_object_paragraph(txt, pos);
210 Filerange p2 = text_object_paragraph(txt, p1.end);
211 return text_range_union(&p1, &p2);
214 static Filerange text_object_bracket(Text *txt, size_t pos, char type) {
215 char c, open, close;
216 int opened = 1, closed = 1;
217 Filerange r = text_range_empty();
219 switch (type) {
220 case '(': case ')': open = '('; close = ')'; break;
221 case '{': case '}': open = '{'; close = '}'; break;
222 case '[': case ']': open = '['; close = ']'; break;
223 case '<': case '>': open = '<'; close = '>'; break;
224 case '"': open = '"'; close = '"'; break;
225 case '`': open = '`'; close = '`'; break;
226 case '\'': open = '\''; close = '\''; break;
227 default: return r;
230 Iterator it = text_iterator_get(txt, pos);
232 if (open == close && text_iterator_byte_get(&it, &c) && (c == '"' || c == '`' || c == '\'')) {
233 size_t match = text_bracket_match(txt, pos, NULL);
234 r.start = MIN(pos, match) + 1;
235 r.end = MAX(pos, match);
236 return r;
239 while (text_iterator_byte_get(&it, &c)) {
240 if (c == open && --opened == 0) {
241 r.start = it.pos + 1;
242 break;
243 } else if (c == close && it.pos != pos) {
244 opened++;
246 text_iterator_byte_prev(&it, NULL);
249 it = text_iterator_get(txt, pos);
250 while (text_iterator_byte_get(&it, &c)) {
251 if (c == close && --closed == 0) {
252 r.end = it.pos;
253 break;
254 } else if (c == open && it.pos != pos) {
255 closed++;
257 text_iterator_byte_next(&it, NULL);
260 if (!text_range_valid(&r))
261 return text_range_empty();
262 return r;
265 Filerange text_object_square_bracket(Text *txt, size_t pos) {
266 return text_object_bracket(txt, pos, ']');
269 Filerange text_object_curly_bracket(Text *txt, size_t pos) {
270 return text_object_bracket(txt, pos, '}');
273 Filerange text_object_angle_bracket(Text *txt, size_t pos) {
274 return text_object_bracket(txt, pos, '>');
277 Filerange text_object_parenthesis(Text *txt, size_t pos) {
278 return text_object_bracket(txt, pos, ')');
281 Filerange text_object_quote(Text *txt, size_t pos) {
282 return text_object_bracket(txt, pos, '"');
285 Filerange text_object_single_quote(Text *txt, size_t pos) {
286 return text_object_bracket(txt, pos, '\'');
289 Filerange text_object_backtick(Text *txt, size_t pos) {
290 return text_object_bracket(txt, pos, '`');
293 Filerange text_object_search_forward(Text *txt, size_t pos, Regex *regex) {
294 size_t start = pos;
295 size_t end = text_size(txt);
296 RegexMatch match[1];
297 bool found = start < end && !text_search_range_forward(txt, start, end - start, regex, 1, match, 0);
298 if (found)
299 return text_range_new(match[0].start, match[0].end);
300 return text_range_empty();
303 Filerange text_object_search_backward(Text *txt, size_t pos, Regex *regex) {
304 size_t start = 0;
305 size_t end = pos;
306 RegexMatch match[1];
307 bool found = !text_search_range_backward(txt, start, end, regex, 1, match, 0);
308 if (found)
309 return text_range_new(match[0].start, match[0].end);
310 return text_range_empty();
313 Filerange text_object_indentation(Text *txt, size_t pos) {
314 char c;
315 size_t bol = text_line_begin(txt, pos);
316 size_t sol = text_line_start(txt, bol);
317 size_t start = bol;
318 size_t end = text_line_next(txt, bol);
319 size_t line_indent = sol - bol;
320 bool line_empty = text_byte_get(txt, bol, &c) && c == '\n';
322 char *buf = text_bytes_alloc0(txt, bol, line_indent);
323 char *tmp = malloc(line_indent);
325 if (!buf || !tmp) {
326 free(buf);
327 free(tmp);
328 return text_range_empty();
331 while ((bol = text_line_begin(txt, text_line_prev(txt, start))) != start) {
332 sol = text_line_start(txt, bol);
333 size_t indent = sol - bol;
334 if (indent < line_indent)
335 break;
336 bool empty = text_byte_get(txt, bol, &c) && c == '\n';
337 if (line_empty && !empty)
338 break;
339 if (line_indent == 0 && empty)
340 break;
341 text_bytes_get(txt, bol, line_indent, tmp);
342 if (memcmp(buf, tmp, line_indent))
343 break;
344 start = bol;
347 do {
348 bol = end;
349 sol = text_line_start(txt, bol);
350 size_t indent = sol - bol;
351 if (indent < line_indent)
352 break;
353 bool empty = text_byte_get(txt, bol, &c) && c == '\n';
354 if (line_empty && !empty)
355 break;
356 if (line_indent == 0 && empty)
357 break;
358 text_bytes_get(txt, bol, line_indent, tmp);
359 if (memcmp(buf, tmp, line_indent))
360 break;
361 end = text_line_next(txt, bol);
362 } while (bol != end);
364 free(buf);
365 free(tmp);
366 return text_range_new(start, end);
369 Filerange text_range_linewise(Text *txt, Filerange *rin) {
370 Filerange rout = *rin;
371 rout.start = text_line_begin(txt, rin->start);
372 if (rin->end != text_line_begin(txt, rin->end))
373 rout.end = text_line_next(txt, rin->end);
374 return rout;
377 bool text_range_is_linewise(Text *txt, Filerange *r) {
378 return text_range_size(r) > 0 &&
379 r->start == text_line_begin(txt, r->start) &&
380 r->end == text_line_begin(txt, r->end);
383 Filerange text_range_inner(Text *txt, Filerange *rin) {
384 char c;
385 Filerange r = *rin;
386 Iterator it = text_iterator_get(txt, rin->start);
387 while (text_iterator_byte_get(&it, &c) && space(c))
388 text_iterator_byte_next(&it, NULL);
389 r.start = it.pos;
390 it = text_iterator_get(txt, rin->end);
391 do r.end = it.pos; while (text_iterator_byte_prev(&it, &c) && space(c));
392 return r;