dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 50.4% 124 / 2 / 248
Functions: 66.7% 10 / 0 / 15
Branches: 40.6% 52 / 6 / 134

src/syntax/highlight.c
Line Branch Exec Source
1 #include <stdint.h>
2 #include <string.h>
3 #include <sys/types.h>
4 #include "highlight.h"
5 #include "syntax/merge.h"
6 #include "util/arith.h"
7 #include "util/bit.h"
8 #include "util/intern.h"
9 #include "util/xmalloc.h"
10 #include "util/xstring.h"
11
12 1 static bool state_is_valid(const State *st)
13 {
14 1 return ((uintptr_t)st & 1) == 0;
15 }
16
17 1 static void mark_state_invalid(void **ptrs, size_t idx)
18 {
19 1 const State *st = ptrs[idx];
20 1 ptrs[idx] = (State*)((uintptr_t)st | 1);
21 1 }
22
23 1 static bool states_equal(void **ptrs, size_t idx, const State *b)
24 {
25 1 const State *a = (State*)((uintptr_t)ptrs[idx] & ~(uintptr_t)1);
26 1 return a == b;
27 }
28
29 1 static bool bufis(const ConditionData *u, const char *buf, size_t len)
30 {
31 1 size_t ulen = u->str.len;
32
2/4
✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 6 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1 time.
1 return (ulen == len) && mem_equal(u->str.buf, buf, len);
33 }
34
35 ✗ static bool bufis_icase(const ConditionData *u, const char *buf, size_t len)
36 {
37 ✗ size_t ulen = u->str.len;
38 ✗ return (ulen == len) && mem_equal_icase(u->str.buf, buf, len);
39 }
40
41 ✗ static State *handle_heredoc (
42 Syntax *syn,
43 State *state,
44 const StyleMap *sm,
45 const char *delim,
46 size_t len
47 ) {
48 ✗ delim = mem_intern(delim, len);
49 ✗ for (size_t i = 0, n = state->heredoc.states.count; i < n; i++) {
50 ✗ HeredocState *s = state->heredoc.states.ptrs[i];
51 ✗ if (interned_strings_equal(s->delim, delim)) {
52 ✗ BUG_ON(s->len != len);
53 ✗ return s->state;
54 }
55 }
56
57 ✗ SyntaxMerge m = {
58 ✗ .subsyn = state->heredoc.subsyntax,
59 ✗ .return_state = state->default_action.destination,
60 .delim = delim,
61 .delim_len = len
62 };
63
64 ✗ HeredocState *s = xmalloc(sizeof(*s));
65 ✗ *s = (HeredocState) {
66 ✗ .state = merge_syntax(syn, &m, sm),
67 .delim = delim,
68 .len = len,
69 };
70
71 ✗ ptr_array_append(&state->heredoc.states, s);
72 ✗ return s->state;
73 }
74
75 // Line should be terminated with \n unless it's the last line
76 8 static const TermStyle **highlight_line (
77 Syntax *syn,
78 State *state,
79 const StyleMap *sm,
80 StringView line_sv,
81 State **ret
82 ) {
83 8 static const TermStyle **styles; // NOLINT(*-avoid-non-const-global-variables)
84 8 static size_t alloc; // NOLINT(*-avoid-non-const-global-variables)
85 8 const char *const line = line_sv.data;
86 8 const size_t len = line_sv.length;
87 8 size_t i = 0;
88 8 ssize_t sidx = -1;
89
90
2/2
✓ Branch 2 → 3 taken 7 times.
✓ Branch 2 → 4 taken 1 time.
8 if (len > alloc) {
91 1 alloc = next_multiple(len, 128);
92 1 styles = xrenew(styles, alloc);
93 }
94
95 8 top:
96
2/2
✓ Branch 8 → 9 taken 8 times.
✓ Branch 8 → 12 taken 242 times.
250 if (i >= len) {
97 8 BUG_ON(i > len);
98 8 *ret = state;
99 8 return styles;
100 }
101
102
2/2
✓ Branch 66 → 13 taken 955 times.
✓ Branch 66 → 67 taken 104 times.
1059 for (size_t ci = 0, n = state->conds.count; ci < n; ci++) {
103 955 const Condition *cond = state->conds.ptrs[ci];
104 955 const ConditionData *u = &cond->u;
105 955 const ConditionType condtype = cond->type;
106 955 const TermStyle *style = cond->a.emit_style;
107 955 State *dest = cond->a.destination;
108
6/13
✓ Branch 13 → 14 taken 225 times.
✓ Branch 13 → 18 taken 1 time.
✗ Branch 13 → 23 not taken.
✓ Branch 13 → 28 taken 92 times.
✓ Branch 13 → 30 taken 448 times.
✓ Branch 13 → 32 taken 78 times.
✗ Branch 13 → 40 not taken.
✗ Branch 13 → 41 not taken.
✗ Branch 13 → 43 not taken.
✗ Branch 13 → 48 not taken.
✓ Branch 13 → 53 taken 111 times.
✗ Branch 13 → 57 not taken.
✗ Branch 13 → 63 not taken.
955 switch (condtype) {
109 225 case COND_CHAR_BUFFER:
110
2/2
✓ Branch 14 → 15 taken 82 times.
✓ Branch 14 → 65 taken 143 times.
225 if (!bitset_contains(u->bitset, line[i])) {
111 break;
112 }
113
2/2
✓ Branch 15 → 16 taken 14 times.
✓ Branch 15 → 17 taken 68 times.
82 if (sidx < 0) {
114 14 sidx = i;
115 }
116 82 styles[i++] = style;
117 82 state = dest;
118 82 goto top;
119 1 case COND_BUFIS:
120
2/4
✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 65 not taken.
✓ Branch 20 → 21 taken 1 time.
✗ Branch 20 → 65 not taken.
1 if (sidx < 0 || !bufis(u, line + sidx, i - sidx)) {
121 break;
122 }
123 1 set_style_range(styles, style, sidx, i);
124 1 sidx = -1;
125 1 state = dest;
126 1 goto top;
127 ✗ case COND_BUFIS_ICASE:
128 ✗ if (sidx < 0 || !bufis_icase(u, line + sidx, i - sidx)) {
129 break;
130 }
131 ✗ set_style_range(styles, style, sidx, i);
132 ✗ sidx = -1;
133 ✗ state = dest;
134 ✗ goto top;
135 92 case COND_CHAR:
136
2/2
✓ Branch 28 → 29 taken 19 times.
✓ Branch 28 → 65 taken 73 times.
92 if (!bitset_contains(u->bitset, line[i])) {
137 break;
138 }
139 19 styles[i++] = style;
140 19 sidx = -1;
141 19 state = dest;
142 19 goto top;
143 448 case COND_CHAR1:
144
2/2
✓ Branch 30 → 31 taken 28 times.
✓ Branch 30 → 65 taken 420 times.
448 if (u->ch != line[i]) {
145 break;
146 }
147 28 styles[i++] = style;
148 28 sidx = -1;
149 28 state = dest;
150 28 goto top;
151 78 case COND_INLIST:
152 case COND_INLIST_BUFFER:
153
2/2
✓ Branch 32 → 33 taken 75 times.
✓ Branch 32 → 65 taken 3 times.
78 if (sidx >= 0) {
154 75 StringView str = string_view(line + sidx, i - sidx);
155
2/2
✓ Branch 34 → 35 taken 8 times.
✓ Branch 34 → 39 taken 67 times.
75 if (hashset_get(&u->str_list->strings, str)) {
156 8 set_style_range(styles, style, sidx, i);
157
1/2
✗ Branch 36 → 37 not taken.
✓ Branch 36 → 38 taken 8 times.
8 sidx = (condtype == COND_INLIST) ? -1 : sidx;
158 8 state = dest;
159 8 goto top;
160 }
161 }
162 break;
163 ✗ case COND_RECOLOR:
164 ✗ set_style_range(styles, style, saturating_subtract(i, u->recolor_len), i);
165 ✗ break;
166 ✗ case COND_RECOLOR_BUFFER:
167 ✗ if (sidx >= 0) {
168 ✗ set_style_range(styles, style, sidx, i);
169 ✗ sidx = -1;
170 }
171 break;
172 ✗ case COND_STR: {
173 ✗ size_t slen = u->str.len;
174 ✗ size_t end = i + slen;
175 ✗ if (len < end || !mem_equal(u->str.buf, line + i, slen)) {
176 break;
177 }
178 ✗ i += set_style_range(styles, style, i, end);
179 ✗ sidx = -1;
180 ✗ state = dest;
181 ✗ goto top;
182 }
183 ✗ case COND_STR_ICASE: {
184 ✗ size_t slen = u->str.len;
185 ✗ size_t end = i + slen;
186 ✗ if (len < end || !mem_equal_icase(u->str.buf, line + i, slen)) {
187 break;
188 }
189 ✗ i += set_style_range(styles, style, i, end);
190 ✗ sidx = -1;
191 ✗ state = dest;
192 ✗ goto top;
193 }
194 111 case COND_STR2:
195 // Optimized COND_STR (length 2, case sensitive)
196
2/4
✓ Branch 53 → 54 taken 111 times.
✗ Branch 53 → 65 not taken.
✗ Branch 55 → 56 not taken.
✓ Branch 55 → 65 taken 111 times.
111 if (len < i + 2 || !mem_equal(u->str.buf, line + i, 2)) {
197 break;
198 }
199 ✗ styles[i++] = style;
200 ✗ styles[i++] = style;
201 ✗ sidx = -1;
202 ✗ state = dest;
203 ✗ goto top;
204 ✗ case COND_HEREDOCEND: {
205 ✗ StringView hde = u->heredocend;
206 ✗ size_t end = i + hde.length;
207 ✗ if (len >= end && mem_equal(hde.data, line + i, hde.length)) {
208 ✗ i += set_style_range(styles, style, i, end);
209 ✗ sidx = -1;
210 ✗ state = dest;
211 ✗ goto top;
212 }
213 ✗ } break;
214 ✗ default:
215 − BUG("unhandled condition type");
216 }
217 }
218
219
3/5
✓ Branch 67 → 68 taken 18 times.
✓ Branch 67 → 69 taken 82 times.
✓ Branch 67 → 70 taken 4 times.
✗ Branch 67 → 71 not taken.
✗ Branch 67 → 74 not taken.
104 switch (state->type) {
220 82 case STATE_EAT:
221 82 styles[i++] = state->default_action.emit_style;
222 // Fallthrough
223 case STATE_NOEAT:
224 sidx = -1;
225 // Fallthrough
226 104 case STATE_NOEAT_BUFFER:
227 104 state = state->default_action.destination;
228 104 break;
229 ✗ case STATE_HEREDOCBEGIN:
230 ✗ if (sidx < 0) {
231 ✗ sidx = i;
232 }
233 ✗ state = handle_heredoc(syn, state, sm, line + sidx, i - sidx);
234 ✗ break;
235 ✗ case STATE_INVALID:
236 default:
237 − BUG("unhandled default action type");
238 }
239
240 104 goto top;
241 }
242
243 1 static void resize_line_states(PointerArray *s, size_t count)
244 {
245
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 5 taken 1 time.
1 if (s->alloc < count) {
246 ✗ s->alloc = next_multiple(count, 64);
247 ✗ s->ptrs = xrenew(s->ptrs, s->alloc);
248 }
249 1 }
250
251 ✗ static void move_line_states (
252 PointerArray *s,
253 size_t to,
254 size_t from,
255 size_t count
256 ) {
257 ✗ memmove(s->ptrs + to, s->ptrs + from, count * sizeof(*s->ptrs));
258 ✗ }
259
260 1 static void block_iter_move_down(BlockIter *bi, size_t count)
261 {
262
1/2
✗ Branch 4 → 3 not taken.
✓ Branch 4 → 5 taken 1 time.
1 while (count--) {
263 ✗ block_iter_eat_line(bi);
264 }
265 1 }
266
267 ✗ static ssize_t fill_hole (
268 Syntax *syn,
269 PointerArray *line_start_states,
270 const StyleMap *sm,
271 BlockIter *bi,
272 ssize_t sidx,
273 ssize_t eidx
274 ) {
275 ✗ void **ptrs = line_start_states->ptrs;
276 ✗ ssize_t idx = sidx;
277
278 ✗ while (idx < eidx) {
279 ✗ State *st;
280 ✗ StringView line = block_iter_get_line_with_nl(bi);
281 ✗ block_iter_eat_line(bi);
282 ✗ highlight_line(syn, ptrs[idx++], sm, line, &st);
283
284 ✗ if (ptrs[idx] == st) {
285 // Was not invalidated and didn't change
286 break;
287 }
288
289 ✗ if (states_equal(ptrs, idx, st)) {
290 // Was invalidated and didn't change
291 ✗ ptrs[idx] = st;
292 } else {
293 // Invalidated or not but changed anyway
294 ✗ ptrs[idx] = st;
295 ✗ if (idx == eidx) {
296 ✗ mark_state_invalid(ptrs, idx + 1);
297 }
298 }
299 }
300 ✗ return idx - sidx;
301 }
302
303 1 void hl_fill_start_states (
304 Syntax *syn,
305 PointerArray *line_start_states,
306 const StyleMap *sm,
307 BlockIter *bi,
308 size_t line_nr
309 ) {
310
1/2
✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 22 not taken.
1 if (!syn) {
311 return;
312 }
313
314 1 PointerArray *s = line_start_states;
315 1 ssize_t current_line = 0;
316 1 ssize_t idx = 0;
317
318 // NOTE: "+ 2" so that you don't have to worry about overflow in fill_hole()
319 1 resize_line_states(s, line_nr + 2);
320 1 State **states = (State **)s->ptrs;
321
322 // Update invalid
323 1 ssize_t last = line_nr;
324
1/2
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 7 not taken.
1 if (last >= s->count) {
325 1 last = s->count - 1;
326 }
327 ✗ while (1) {
328
3/4
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 11 taken 1 time.
✓ Branch 10 → 6 taken 1 time.
✗ Branch 10 → 11 not taken.
2 while (idx <= last && state_is_valid(states[idx])) {
329 1 idx++;
330 }
331
1/2
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 15 taken 1 time.
1 if (idx > last) {
332 break;
333 }
334
335 // Go to line before first hole
336 ✗ idx--;
337 ✗ block_iter_move_down(bi, idx - current_line);
338 ✗ current_line = idx;
339
340 // NOTE: might not fill entire hole, which is ok
341 ✗ ssize_t count = fill_hole(syn, s, sm, bi, idx, last);
342 ✗ idx += count;
343 ✗ current_line += count;
344 }
345
346 // Add new
347 1 block_iter_move_down(bi, s->count - 1 - current_line);
348
2/2
✓ Branch 21 → 17 taken 7 times.
✓ Branch 21 → 22 taken 1 time.
8 while (s->count - 1 < line_nr) {
349 7 StringView line = block_iter_get_line_with_nl(bi);
350 7 highlight_line (
351 syn,
352 7 states[s->count - 1],
353 sm,
354 line,
355 7 &states[s->count]
356 );
357 7 s->count++;
358 7 block_iter_eat_line(bi);
359 }
360 }
361
362 1 const TermStyle **hl_line (
363 Syntax *syn,
364 PointerArray *line_start_states,
365 const StyleMap *sm,
366 StringView line,
367 size_t line_nr,
368 bool *next_changed
369 ) {
370 1 *next_changed = false;
371
1/2
✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 14 not taken.
1 if (!syn) {
372 return NULL;
373 }
374
375 1 PointerArray *s = line_start_states;
376 1 BUG_ON(line_nr >= s->count);
377 1 State *next;
378 1 const TermStyle **styles = highlight_line(syn, s->ptrs[line_nr++], sm, line, &next);
379
380
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 9 taken 1 time.
1 if (line_nr == s->count) {
381 ✗ resize_line_states(s, s->count + 1);
382 ✗ s->ptrs[s->count++] = next;
383 ✗ *next_changed = true;
384
1/2
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 14 not taken.
1 } else if (s->ptrs[line_nr] == next) {
385 // Was not invalidated and didn't change
386
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 1 time.
1 } else if (states_equal(s->ptrs, line_nr, next)) {
387 // Was invalidated and didn't change
388 ✗ s->ptrs[line_nr] = next;
389 // *next_changed = 1;
390 } else {
391 // Invalidated or not but changed anyway
392 1 s->ptrs[line_nr] = next;
393 1 *next_changed = true;
394
1/2
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 14 not taken.
1 if (line_nr + 1 < s->count) {
395 1 mark_state_invalid(s->ptrs, line_nr + 1);
396 }
397 }
398 return styles;
399 }
400
401 // Called after text has been inserted to re-highlight changed lines
402 2 void hl_insert(PointerArray *line_start_states, size_t first, size_t lines)
403 {
404 2 PointerArray *s = line_start_states;
405
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 13 taken 1 time.
2 if (first >= s->count) {
406 // Nothing to re-highlight
407 return;
408 }
409
410 1 size_t last = first + lines;
411
1/2
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 5 not taken.
1 if (last + 1 >= s->count) {
412 // Last already highlighted lines changed; there's nothing to
413 // gain, so throw them away
414 1 s->count = first + 1;
415 1 return;
416 }
417
418 // Add room for new line states
419 ✗ if (lines) {
420 ✗ size_t to = last + 1;
421 ✗ size_t from = first + 1;
422 ✗ resize_line_states(s, s->count + lines);
423 ✗ move_line_states(s, to, from, s->count - from);
424 ✗ s->count += lines;
425 }
426
427 // Invalidate start states of new and changed lines
428 ✗ for (size_t i = first + 1; i <= last + 1; i++) {
429 ✗ mark_state_invalid(s->ptrs, i);
430 }
431 }
432
433 // Called after text has been deleted to re-highlight changed lines
434 ✗ void hl_delete(PointerArray *line_start_states, size_t first, size_t lines)
435 {
436 ✗ PointerArray *s = line_start_states;
437 ✗ if (s->count == 1) {
438 return;
439 }
440
441 ✗ if (first >= s->count) {
442 // Nothing to highlight
443 return;
444 }
445
446 ✗ size_t last = first + lines;
447 ✗ if (last + 1 >= s->count) {
448 // Last already highlighted lines changed; there's nothing to
449 // gain, so throw them away
450 ✗ s->count = first + 1;
451 ✗ return;
452 }
453
454 // There are already highlighted lines after changed lines; try to
455 // save the work
456
457 // Remove deleted lines (states)
458 ✗ if (lines) {
459 ✗ size_t to = first + 1;
460 ✗ size_t from = last + 1;
461 ✗ move_line_states(s, to, from, s->count - from);
462 ✗ s->count -= lines;
463 }
464
465 // Invalidate line start state after the changed line
466 ✗ mark_state_invalid(s->ptrs, first + 1);
467 }
468