dte test coverage


Directory: ./
File: src/block-iter.c
Date: 2025-12-11 10:43:49
Coverage Exec Excl Total
Lines: 91.3% 188 0 206
Functions: 100.0% 21 0 21
Branches: 75.0% 63 0 84

Line Branch Exec Source
1 #include <string.h>
2 #include "block-iter.h"
3 #include "util/ascii.h"
4 #include "util/debug.h"
5 #include "util/utf8.h"
6 #include "util/xmalloc.h"
7 #include "util/xmemrchr.h"
8
9 NONNULL_ARGS_AND_RETURN
10 548 static const char *block_memchr_eol(const Block *blk, size_t offset)
11 {
12 548 BUG_ON(offset > blk->size);
13 548 const char *eol = memchr(blk->data + offset, '\n', blk->size - offset);
14 548 BUG_ON(!eol);
15 548 return eol;
16 }
17
18 // Move after next newline (beginning of next line or end of file)
19 // and return number of bytes moved
20 135 size_t block_iter_eat_line(BlockIter *bi)
21 {
22 135 block_iter_normalize(bi);
23 135 const size_t offset = bi->offset;
24
2/2
✓ Branch 3 → 4 taken 132 times.
✓ Branch 3 → 9 taken 3 times.
135 if (unlikely(offset == bi->blk->size)) {
25 return 0;
26 }
27
28 // There must be at least one newline
29
2/2
✓ Branch 4 → 5 taken 7 times.
✓ Branch 4 → 6 taken 125 times.
132 if (bi->blk->nl == 1) {
30 7 bi->offset = bi->blk->size;
31 } else {
32 125 const char *end = block_memchr_eol(bi->blk, offset);
33 125 bi->offset = (size_t)(end + 1 - bi->blk->data);
34 }
35
36 132 return bi->offset - offset;
37 }
38
39 // Move to beginning of next line (if any) and return number of bytes moved
40 79 size_t block_iter_next_line(BlockIter *bi)
41 {
42 79 block_iter_normalize(bi);
43 79 const size_t offset = bi->offset;
44
1/2
✓ Branch 3 → 4 taken 79 times.
✗ Branch 3 → 10 not taken.
79 if (unlikely(offset == bi->blk->size)) {
45 return 0;
46 }
47
48 // There must be at least one newline
49 79 size_t new_offset;
50
1/2
✓ Branch 4 → 5 taken 79 times.
✗ Branch 4 → 7 not taken.
79 if (bi->blk->nl == 1) {
51 new_offset = bi->blk->size;
52 } else {
53 79 const char *end = block_memchr_eol(bi->blk, offset);
54 79 new_offset = (size_t)(end + 1 - bi->blk->data);
55 }
56
57
3/4
✓ Branch 7 → 8 taken 15 times.
✓ Branch 7 → 9 taken 64 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 15 times.
79 if (new_offset == bi->blk->size && bi->blk->node.next == bi->head) {
58 return 0;
59 }
60
61 64 bi->offset = new_offset;
62 64 return bi->offset - offset;
63 }
64
65 // Move to beginning of previous line (if any) and return number of bytes moved
66 101 size_t block_iter_prev_line(BlockIter *bi)
67 {
68 101 CodePoint u;
69 101 BlockIter tmp = *bi;
70 101 size_t n1 = block_iter_bol(&tmp);
71 101 size_t n2 = block_iter_prev_char(&tmp, &u);
72
2/2
✓ Branch 4 → 5 taken 91 times.
✓ Branch 4 → 11 taken 10 times.
101 if (n2 == 0) {
73 // block_iter_bol() moved to BOF, which means there's no previous
74 // line and we leave `bi` unchanged
75 return 0;
76 }
77
78 // Otherwise, block_iter_prev_char() moved to the previous line's EOL
79 91 BUG_ON(n2 != 1);
80 91 BUG_ON(u != '\n');
81
82 // So we move to BOL again, set the in-out parameter to reflect the
83 // temporary iterator and return the sum of the 3 movements
84 91 size_t n3 = block_iter_bol(&tmp);
85 91 *bi = tmp;
86 91 return n1 + n2 + n3;
87 }
88
89 123 size_t block_iter_get_char(const BlockIter *bi, CodePoint *up)
90 {
91 123 BlockIter tmp = *bi;
92 123 return block_iter_next_char(&tmp, up);
93 }
94
95 291 size_t block_iter_next_char(BlockIter *bi, CodePoint *up)
96 {
97 291 size_t offset = bi->offset;
98
2/2
✓ Branch 2 → 3 taken 17 times.
✓ Branch 2 → 5 taken 274 times.
291 if (unlikely(offset == bi->blk->size)) {
99
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 9 taken 17 times.
17 if (unlikely(bi->blk->node.next == bi->head)) {
100 return 0;
101 }
102 bi->blk = BLOCK(bi->blk->node.next);
103 bi->offset = offset = 0;
104 }
105
106 // Note: this block can't be empty
107 274 unsigned char byte = bi->blk->data[offset];
108
2/2
✓ Branch 5 → 6 taken 272 times.
✓ Branch 5 → 7 taken 2 times.
274 if (likely(byte < 0x80)) {
109 272 *up = byte;
110 272 bi->offset++;
111 272 return 1;
112 }
113
114 2 *up = u_get_nonascii(bi->blk->data, bi->blk->size, &bi->offset);
115 2 return bi->offset - offset;
116 }
117
118 195 size_t block_iter_prev_char(BlockIter *bi, CodePoint *up)
119 {
120 195 size_t offset = bi->offset;
121
2/2
✓ Branch 2 → 3 taken 16 times.
✓ Branch 2 → 5 taken 179 times.
195 if (unlikely(offset == 0)) {
122
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 9 taken 16 times.
16 if (unlikely(bi->blk->node.prev == bi->head)) {
123 return 0;
124 }
125 bi->blk = BLOCK(bi->blk->node.prev);
126 bi->offset = offset = bi->blk->size;
127 }
128
129 // Note: this block can't be empty
130 179 unsigned char byte = bi->blk->data[offset - 1];
131
1/2
✓ Branch 5 → 6 taken 179 times.
✗ Branch 5 → 7 not taken.
179 if (likely(byte < 0x80)) {
132 179 *up = byte;
133 179 bi->offset--;
134 179 return 1;
135 }
136
137 *up = u_prev_char(bi->blk->data, &bi->offset);
138 return offset - bi->offset;
139 }
140
141 45 size_t block_iter_next_column(BlockIter *bi)
142 {
143 45 CodePoint u;
144 45 size_t size = block_iter_next_char(bi, &u);
145
3/4
✓ Branch 7 → 8 taken 38 times.
✓ Branch 7 → 10 taken 7 times.
✗ Branch 9 → 4 not taken.
✓ Branch 9 → 10 taken 38 times.
45 while (block_iter_get_char(bi, &u) && u_is_zero_width(u)) {
146 size += block_iter_next_char(bi, &u);
147 }
148 45 return size;
149 }
150
151 16 size_t block_iter_prev_column(BlockIter *bi)
152 {
153 16 CodePoint u;
154 16 size_t skip, total = 0;
155 16 do {
156 16 skip = block_iter_prev_char(bi, &u);
157 16 total += skip;
158
3/4
✓ Branch 4 → 5 taken 13 times.
✓ Branch 4 → 8 taken 3 times.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 13 times.
16 } while (skip && u_is_zero_width(u));
159 16 return total;
160 }
161
162 424 size_t block_iter_bol(BlockIter *bi)
163 {
164 424 block_iter_normalize(bi);
165 424 size_t offset = bi->offset;
166
2/2
✓ Branch 3 → 4 taken 198 times.
✓ Branch 3 → 13 taken 226 times.
424 if (block_iter_is_bol(bi)) {
167 return 0;
168 }
169
170 // These cases are handled by the condition above
171 198 const Block *blk = bi->blk;
172 198 BUG_ON(offset == 0);
173 198 BUG_ON(offset >= blk->size);
174
175
2/2
✓ Branch 8 → 9 taken 31 times.
✓ Branch 8 → 10 taken 167 times.
198 if (blk->nl == 1) {
176 31 bi->offset = 0; // Only 1 line in `blk`; bol is at offset 0
177 31 return offset;
178 }
179
180 167 const char *nl = xmemrchr(blk->data, '\n', offset - 1);
181
2/2
✓ Branch 10 → 11 taken 44 times.
✓ Branch 10 → 12 taken 123 times.
167 if (!nl) {
182 44 bi->offset = 0; // No newline before offset; bol is at offset 0
183 44 return offset;
184 }
185
186 123 offset = (size_t)(nl - blk->data) + 1;
187 123 size_t count = bi->offset - offset;
188 123 bi->offset = offset;
189 123 return count;
190 }
191
192 28 size_t block_iter_eol(BlockIter *bi)
193 {
194 28 block_iter_normalize(bi);
195 28 const Block *blk = bi->blk;
196 28 const size_t offset = bi->offset;
197
198
2/2
✓ Branch 3 → 4 taken 25 times.
✓ Branch 3 → 8 taken 3 times.
28 if (unlikely(offset == blk->size)) {
199 // Cursor at end of last block
200 return 0;
201 }
202
203
2/2
✓ Branch 4 → 5 taken 4 times.
✓ Branch 4 → 6 taken 21 times.
25 if (blk->nl == 1) {
204 4 bi->offset = blk->size - 1;
205 4 return bi->offset - offset;
206 }
207
208 21 const char *end = block_memchr_eol(blk, offset);
209 21 bi->offset = (size_t)(end - blk->data);
210 21 return bi->offset - offset;
211 }
212
213 // Count spaces and tabs at or after iterator (and move beyond them)
214 30 size_t block_iter_skip_blanks_fwd(BlockIter *bi)
215 {
216 30 block_iter_normalize(bi);
217 30 const char *data = bi->blk->data;
218 30 size_t count = 0;
219 30 size_t i = bi->offset;
220
221 // We're only operating on one line and checking for ASCII characters,
222 // so Block traversal and Unicode-aware decoding are both unnecessary
223
1/2
✓ Branch 6 → 4 taken 52 times.
✗ Branch 6 → 7 not taken.
52 for (size_t n = bi->blk->size; i < n; count++) {
224 52 unsigned char c = data[i++];
225
2/2
✓ Branch 4 → 5 taken 22 times.
✓ Branch 4 → 7 taken 30 times.
52 if (!ascii_isblank(c)) {
226 break;
227 }
228 }
229
230 30 bi->offset = i;
231 30 return count;
232 }
233
234 // Count spaces and tabs before iterator (and move to beginning of them)
235 30 size_t block_iter_skip_blanks_bwd(BlockIter *bi)
236
237 {
238 30 block_iter_normalize(bi);
239 30 size_t count = 0;
240 30 size_t i = bi->offset;
241
242
1/2
✓ Branch 6 → 4 taken 61 times.
✗ Branch 6 → 7 not taken.
61 for (const char *data = bi->blk->data; i > 0; count++) {
243 61 unsigned char c = data[--i];
244
2/2
✓ Branch 4 → 5 taken 31 times.
✓ Branch 4 → 7 taken 30 times.
61 if (!ascii_isblank(c)) {
245 i++;
246 break;
247 }
248 }
249
250 30 bi->offset = i;
251 30 return count;
252 }
253
254 // Non-empty line can be used to determine size of indentation for the next line
255 14 bool block_iter_find_non_empty_line_bwd(BlockIter *bi)
256 {
257 14 block_iter_bol(bi);
258 18 do {
259 18 StringView line = block_iter_get_line(bi);
260
2/2
✓ Branch 4 → 5 taken 14 times.
✓ Branch 4 → 6 taken 4 times.
18 if (!strview_isblank(line)) {
261 14 return true;
262 }
263
1/2
✓ Branch 7 → 3 taken 4 times.
✗ Branch 7 → 8 not taken.
4 } while (block_iter_prev_line(bi));
264 return false;
265 }
266
267 1 void block_iter_back_bytes(BlockIter *bi, size_t count)
268 {
269
1/2
✗ Branch 4 → 3 not taken.
✓ Branch 4 → 5 taken 1 time.
1 while (count > bi->offset) {
270 count -= bi->offset;
271 bi->blk = BLOCK(bi->blk->node.prev);
272 bi->offset = bi->blk->size;
273 }
274 1 bi->offset -= count;
275 1 }
276
277 333 void block_iter_skip_bytes(BlockIter *bi, size_t count)
278 {
279 333 size_t avail = bi->blk->size - bi->offset;
280
1/2
✗ Branch 4 → 3 not taken.
✓ Branch 4 → 5 taken 333 times.
333 while (count > avail) {
281 count -= avail;
282 bi->blk = BLOCK(bi->blk->node.next);
283 bi->offset = 0;
284 avail = bi->blk->size;
285 }
286 333 bi->offset += count;
287 333 }
288
289 63 void block_iter_goto_offset(BlockIter *bi, size_t offset)
290 {
291 63 Block *blk;
292
1/2
✓ Branch 6 → 3 taken 63 times.
✗ Branch 6 → 7 not taken.
63 block_for_each(blk, bi->head) {
293
1/2
✓ Branch 3 → 4 taken 63 times.
✗ Branch 3 → 5 not taken.
63 if (offset <= blk->size) {
294 63 bi->blk = blk;
295 63 bi->offset = offset;
296 63 return;
297 }
298 offset -= blk->size;
299 }
300 }
301
302 4 void block_iter_goto_line(BlockIter *bi, size_t line)
303 {
304 4 Block *blk = BLOCK(bi->head->next);
305 4 size_t nl = 0;
306
1/4
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 4 times.
✗ Branch 5 → 3 not taken.
✗ Branch 5 → 6 not taken.
4 while (blk->node.next != bi->head && nl + blk->nl < line) {
307 nl += blk->nl;
308 blk = BLOCK(blk->node.next);
309 }
310
311 4 bi->blk = blk;
312 4 bi->offset = 0;
313
2/2
✓ Branch 10 → 7 taken 8 times.
✓ Branch 10 → 11 taken 3 times.
11 while (nl < line) {
314
2/2
✓ Branch 8 → 9 taken 7 times.
✓ Branch 8 → 11 taken 1 time.
8 if (!block_iter_eat_line(bi)) {
315 break;
316 }
317 7 nl++;
318 }
319 4 }
320
321 328 size_t block_iter_get_offset(const BlockIter *bi)
322 {
323 328 const Block *blk;
324 328 size_t offset = 0;
325
1/2
✓ Branch 5 → 3 taken 328 times.
✗ Branch 5 → 6 not taken.
328 block_for_each(blk, bi->head) {
326
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 328 times.
328 if (blk == bi->blk) {
327 break;
328 }
329 offset += blk->size;
330 }
331 328 return offset + bi->offset;
332 }
333
334 13 char *block_iter_get_bytes(const BlockIter *bi, size_t len)
335 {
336
1/2
✓ Branch 2 → 3 taken 13 times.
✗ Branch 2 → 10 not taken.
13 if (len == 0) {
337 return NULL;
338 }
339
340 13 const Block *blk = bi->blk;
341 13 size_t offset = bi->offset;
342 13 size_t pos = 0;
343 13 char *buf = xmalloc(len + 1); // +1 byte; so expand_word() can append '\0'
344
345
2/2
✓ Branch 9 → 5 taken 13 times.
✓ Branch 9 → 10 taken 13 times.
26 while (pos < len) {
346 13 const size_t avail = blk->size - offset;
347 13 size_t count = MIN(len - pos, avail);
348 13 memcpy(buf + pos, blk->data + offset, count);
349 13 pos += count;
350 13 BUG_ON(pos < len && blk->node.next == bi->head);
351 13 blk = BLOCK(blk->node.next);
352 13 offset = 0;
353 }
354
355 return buf;
356 }
357
358 // Return the contents of the line that extends from `bi`. Callers
359 // should ensure `bi` is already at BOL, if whole lines are needed.
360 381 StringView block_iter_get_line_with_nl(BlockIter *bi)
361 {
362 381 block_iter_normalize(bi);
363 381 const char *start = bi->blk->data + bi->offset;
364 381 StringView line = {.data = start, .length = 0};
365 381 size_t max = bi->blk->size - bi->offset;
366
367
2/2
✓ Branch 3 → 4 taken 10 times.
✓ Branch 3 → 5 taken 371 times.
381 if (unlikely(max == 0)) {
368 // Cursor at end of last block
369 10 return line;
370 }
371
372
2/2
✓ Branch 5 → 6 taken 48 times.
✓ Branch 5 → 9 taken 323 times.
371 if (bi->blk->nl == 1) {
373 // Block contains only 1 line; end-of-line is end-of-block
374 48 BUG_ON(line.data[max - 1] != '\n');
375 48 line.length = max;
376 48 return line;
377 }
378
379 323 const char *nl = block_memchr_eol(bi->blk, bi->offset);
380 323 line.length = (size_t)(nl - start + 1);
381 323 BUG_ON(line.length == 0);
382 323 return line;
383 }
384