dte test coverage


Directory: ./
File: src/syntax/highlight.c
Date: 2025-02-14 16:55:22
Exec Total Coverage
Lines: 126 253 49.8%
Functions: 11 16 68.8%
Branches: 53 135 39.3%

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