dte test coverage


Directory: ./
File: src/syntax/highlight.c
Date: 2025-06-04 06:50:24
Exec Total Coverage
Lines: 127 249 51.0%
Functions: 11 16 68.8%
Branches: 54 132 40.9%

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