dte test coverage


Directory: ./
File: src/syntax/highlight.c
Date: 2025-07-13 15:27:15
Exec Total Coverage
Lines: 122 244 50.0%
Functions: 10 15 66.7%
Branches: 52 130 40.0%

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 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);
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 const 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 unsigned 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 0 (2→3) taken 7 times.
✓ Branch 1 (2→4) taken 1 times.
8 if (len > alloc) {
91 1 alloc = next_multiple(len, 128);
92 1 styles = xrenew(styles, alloc);
93 }
94
95 7 top:
96
2/2
✓ Branch 0 (8→9) taken 8 times.
✓ Branch 1 (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 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++) {
103 918 const Condition *cond = state->conds.ptrs[ci];
104 918 const ConditionData *u = &cond->u;
105 918 const ConditionType condtype = cond->type;
106 918 const TermStyle *style = cond->a.emit_style;
107 918 State *dest = cond->a.destination;
108
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) {
109 225 case COND_CHAR_BUFFER:
110
2/2
✓ Branch 0 (14→15) taken 82 times.
✓ Branch 1 (14→64) taken 143 times.
225 if (!bitset_contains(u->bitset, line[i])) {
111 break;
112 }
113
2/2
✓ Branch 0 (15→16) taken 14 times.
✓ Branch 1 (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 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)) {
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 0 (28→29) taken 19 times.
✓ Branch 1 (28→64) 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 0 (30→31) taken 28 times.
✓ Branch 1 (30→64) 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
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)) {
154 break;
155 }
156 8 set_style_range(styles, style, sidx, i);
157
1/2
✗ Branch 0 (36→37) not taken.
✓ Branch 1 (36→38) taken 8 times.
8 sidx = (condtype == COND_INLIST) ? -1 : sidx;
158 8 state = dest;
159 8 goto top;
160 case COND_RECOLOR:
161 set_style_range(styles, style, saturating_subtract(i, u->recolor_len), i);
162 break;
163 case COND_RECOLOR_BUFFER:
164 if (sidx >= 0) {
165 set_style_range(styles, style, sidx, i);
166 sidx = -1;
167 }
168 break;
169 case COND_STR: {
170 size_t slen = u->str.len;
171 size_t end = i + slen;
172 if (len < end || !mem_equal(u->str.buf, line + i, slen)) {
173 break;
174 }
175 i += set_style_range(styles, style, i, end);
176 sidx = -1;
177 state = dest;
178 goto top;
179 }
180 case COND_STR_ICASE: {
181 size_t slen = u->str.len;
182 size_t end = i + slen;
183 if (len < end || !mem_equal_icase(u->str.buf, line + i, slen)) {
184 break;
185 }
186 i += set_style_range(styles, style, i, end);
187 sidx = -1;
188 state = dest;
189 goto top;
190 }
191 74 case COND_STR2:
192 // Optimized COND_STR (length 2, case sensitive)
193
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)) {
194 break;
195 }
196 styles[i++] = style;
197 styles[i++] = style;
198 sidx = -1;
199 state = dest;
200 goto top;
201 case COND_HEREDOCEND: {
202 const char *str = u->heredocend.data;
203 size_t slen = u->heredocend.length;
204 size_t end = i + slen;
205 if (len >= end && (slen == 0 || mem_equal(str, line + i, slen))) {
206 i += set_style_range(styles, style, i, end);
207 sidx = -1;
208 state = dest;
209 goto top;
210 }
211 } break;
212 default:
213 BUG("unhandled condition type");
214 }
215 }
216
217
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) {
218 82 case STATE_EAT:
219 82 styles[i++] = state->default_action.emit_style;
220 // Fallthrough
221 case STATE_NOEAT:
222 sidx = -1;
223 // Fallthrough
224 104 case STATE_NOEAT_BUFFER:
225 104 state = state->default_action.destination;
226 104 break;
227 case STATE_HEREDOCBEGIN:
228 if (sidx < 0) {
229 sidx = i;
230 }
231 state = handle_heredoc(syn, state, sm, line + sidx, i - sidx);
232 break;
233 case STATE_INVALID:
234 default:
235 BUG("unhandled default action type");
236 }
237
238 104 goto top;
239 }
240
241 1 static void resize_line_states(PointerArray *s, size_t count)
242 {
243
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→5) taken 1 times.
1 if (s->alloc < count) {
244 s->alloc = next_multiple(count, 64);
245 s->ptrs = xrenew(s->ptrs, s->alloc);
246 }
247 1 }
248
249 static void move_line_states (
250 PointerArray *s,
251 size_t to,
252 size_t from,
253 size_t count
254 ) {
255 memmove(s->ptrs + to, s->ptrs + from, count * sizeof(*s->ptrs));
256 }
257
258 1 static void block_iter_move_down(BlockIter *bi, size_t count)
259 {
260
1/2
✗ Branch 0 (4→3) not taken.
✓ Branch 1 (4→5) taken 1 times.
1 while (count--) {
261 block_iter_eat_line(bi);
262 }
263 1 }
264
265 static ssize_t fill_hole (
266 Syntax *syn,
267 PointerArray *line_start_states,
268 const StyleMap *sm,
269 BlockIter *bi,
270 ssize_t sidx,
271 ssize_t eidx
272 ) {
273 void **ptrs = line_start_states->ptrs;
274 ssize_t idx = sidx;
275
276 while (idx < eidx) {
277 State *st;
278 StringView line = block_iter_get_line_with_nl(bi);
279 block_iter_eat_line(bi);
280 highlight_line(syn, ptrs[idx++], sm, &line, &st);
281
282 if (ptrs[idx] == st) {
283 // Was not invalidated and didn't change
284 break;
285 }
286
287 if (states_equal(ptrs, idx, st)) {
288 // Was invalidated and didn't change
289 ptrs[idx] = st;
290 } else {
291 // Invalidated or not but changed anyway
292 ptrs[idx] = st;
293 if (idx == eidx) {
294 mark_state_invalid(ptrs, idx + 1);
295 }
296 }
297 }
298 return idx - sidx;
299 }
300
301 1 void hl_fill_start_states (
302 Syntax *syn,
303 PointerArray *line_start_states,
304 const StyleMap *sm,
305 BlockIter *bi,
306 size_t line_nr
307 ) {
308
1/2
✓ Branch 0 (2→3) taken 1 times.
✗ Branch 1 (2→22) not taken.
1 if (!syn) {
309 return;
310 }
311
312 1 PointerArray *s = line_start_states;
313 1 ssize_t current_line = 0;
314 1 ssize_t idx = 0;
315
316 // NOTE: "+ 2" so that you don't have to worry about overflow in fill_hole()
317 1 resize_line_states(s, line_nr + 2);
318 1 State **states = (State **)s->ptrs;
319
320 // Update invalid
321 1 ssize_t last = line_nr;
322
1/2
✓ Branch 0 (4→5) taken 1 times.
✗ Branch 1 (4→7) not taken.
1 if (last >= s->count) {
323 1 last = s->count - 1;
324 }
325 while (1) {
326
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])) {
327 1 idx++;
328 }
329
1/2
✗ Branch 0 (11→12) not taken.
✓ Branch 1 (11→15) taken 1 times.
1 if (idx > last) {
330 break;
331 }
332
333 // Go to line before first hole
334 idx--;
335 block_iter_move_down(bi, idx - current_line);
336 current_line = idx;
337
338 // NOTE: might not fill entire hole, which is ok
339 ssize_t count = fill_hole(syn, s, sm, bi, idx, last);
340 idx += count;
341 current_line += count;
342 }
343
344 // Add new
345 1 block_iter_move_down(bi, s->count - 1 - current_line);
346
2/2
✓ Branch 0 (21→17) taken 7 times.
✓ Branch 1 (21→22) taken 1 times.
8 while (s->count - 1 < line_nr) {
347 7 StringView line = block_iter_get_line_with_nl(bi);
348 7 highlight_line (
349 syn,
350 7 states[s->count - 1],
351 sm,
352 &line,
353 7 &states[s->count]
354 );
355 7 s->count++;
356 7 block_iter_eat_line(bi);
357 }
358 }
359
360 1 const TermStyle **hl_line (
361 Syntax *syn,
362 PointerArray *line_start_states,
363 const StyleMap *sm,
364 const StringView *line,
365 size_t line_nr,
366 bool *next_changed
367 ) {
368 1 *next_changed = false;
369
1/2
✓ Branch 0 (2→3) taken 1 times.
✗ Branch 1 (2→14) not taken.
1 if (!syn) {
370 return NULL;
371 }
372
373 1 PointerArray *s = line_start_states;
374 1 BUG_ON(line_nr >= s->count);
375 1 State *next;
376 1 const TermStyle **styles = highlight_line(syn, s->ptrs[line_nr++], sm, line, &next);
377
378
1/2
✗ Branch 0 (6→7) not taken.
✓ Branch 1 (6→9) taken 1 times.
1 if (line_nr == s->count) {
379 resize_line_states(s, s->count + 1);
380 s->ptrs[s->count++] = next;
381 *next_changed = true;
382
1/2
✓ Branch 0 (9→10) taken 1 times.
✗ Branch 1 (9→14) not taken.
1 } else if (s->ptrs[line_nr] == next) {
383 // Was not invalidated and didn't change
384
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)) {
385 // Was invalidated and didn't change
386 s->ptrs[line_nr] = next;
387 // *next_changed = 1;
388 } else {
389 // Invalidated or not but changed anyway
390 1 s->ptrs[line_nr] = next;
391 1 *next_changed = true;
392
1/2
✓ Branch 0 (12→13) taken 1 times.
✗ Branch 1 (12→14) not taken.
1 if (line_nr + 1 < s->count) {
393 1 mark_state_invalid(s->ptrs, line_nr + 1);
394 }
395 }
396 return styles;
397 }
398
399 // Called after text has been inserted to re-highlight changed lines
400 2 void hl_insert(PointerArray *line_start_states, size_t first, size_t lines)
401 {
402 2 PointerArray *s = line_start_states;
403
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→13) taken 1 times.
2 if (first >= s->count) {
404 // Nothing to re-highlight
405 return;
406 }
407
408 1 size_t last = first + lines;
409
1/2
✓ Branch 0 (3→4) taken 1 times.
✗ Branch 1 (3→5) not taken.
1 if (last + 1 >= s->count) {
410 // Last already highlighted lines changed; there's nothing to
411 // gain, so throw them away
412 1 s->count = first + 1;
413 1 return;
414 }
415
416 // Add room for new line states
417 if (lines) {
418 size_t to = last + 1;
419 size_t from = first + 1;
420 resize_line_states(s, s->count + lines);
421 move_line_states(s, to, from, s->count - from);
422 s->count += lines;
423 }
424
425 // Invalidate start states of new and changed lines
426 for (size_t i = first + 1; i <= last + 1; i++) {
427 mark_state_invalid(s->ptrs, i);
428 }
429 }
430
431 // Called after text has been deleted to re-highlight changed lines
432 void hl_delete(PointerArray *line_start_states, size_t first, size_t lines)
433 {
434 PointerArray *s = line_start_states;
435 if (s->count == 1) {
436 return;
437 }
438
439 if (first >= s->count) {
440 // Nothing to highlight
441 return;
442 }
443
444 size_t last = first + lines;
445 if (last + 1 >= s->count) {
446 // Last already highlighted lines changed; there's nothing to
447 // gain, so throw them away
448 s->count = first + 1;
449 return;
450 }
451
452 // There are already highlighted lines after changed lines; try to
453 // save the work
454
455 // Remove deleted lines (states)
456 if (lines) {
457 size_t to = first + 1;
458 size_t from = last + 1;
459 move_line_states(s, to, from, s->count - from);
460 s->count -= lines;
461 }
462
463 // Invalidate line start state after the changed line
464 mark_state_invalid(s->ptrs, first + 1);
465 }
466