dte test coverage


Directory: ./
File: src/syntax/highlight.c
Date: 2024-12-21 16:03:22
Exec Total Coverage
Lines: 126 253 49.8%
Functions: 11 16 68.8%
Branches: 52 135 38.5%

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