dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 90.8% 208 / 0 / 229
Functions: 100.0% 9 / 0 / 9
Branches: 78.6% 77 / 40 / 138

src/edit.c
Line Branch Exec Source
1 #include <string.h>
2 #include "edit.h"
3 #include "block.h"
4 #include "buffer.h"
5 #include "syntax/highlight.h"
6 #include "util/debug.h"
7 #include "util/list.h"
8 #include "util/xmalloc.h"
9
10 enum {
11 BLOCK_EDIT_SIZE = 512
12 };
13
14 468 static void sanity_check_blocks(const View *view, bool check_newlines)
15 {
16 468 if (!DEBUG_ASSERTIONS_ENABLED) {
17 return;
18 }
19
20 // NOLINTBEGIN(bugprone-assert-side-effect)
21 468 const Buffer *buffer = view->buffer;
22 468 const Block *blk = buffer_get_first_block(buffer);
23 468 const Block *cursor_blk = view->cursor.blk;
24 468 BUG_ON(list_empty(&buffer->blocks));
25 468 BUG_ON(view->cursor.offset > cursor_blk->size);
26
27
2/2
✓ Branch 6 → 7 taken 19 times.
✓ Branch 6 → 14 taken 449 times.
468 if (blk->size == 0) {
28 // The only time a zero-sized block is valid is when it's the
29 // first and only block
30 19 BUG_ON(block_has_next(blk, &buffer->blocks));
31 19 BUG_ON(block_has_prev(blk, &buffer->blocks));
32 19 BUG_ON(cursor_blk != blk);
33 19 block_sanity_check(blk);
34 19 return;
35 }
36
37 449 unsigned int cursor_seen = 0;
38
2/2
✓ Branch 27 → 15 taken 453 times.
✓ Branch 27 → 28 taken 449 times.
902 block_for_each(blk, &buffer->blocks) {
39 453 block_sanity_check(blk);
40 453 cursor_seen += (blk == cursor_blk);
41 453 BUG_ON(blk->size == 0);
42
43 // Non-empty blocks must ALWAYS end with a newline, since
44 // lines MAY NOT straddle multiple blocks
45 453 BUG_ON(check_newlines && blk->data[blk->size - 1] != '\n');
46 453 BUG_ON(blk->nl < 1);
47 453 BUG_ON(DEBUG > 2 && count_nl(blk->data, blk->size) != blk->nl);
48 }
49
50 449 BUG_ON(cursor_seen != 1);
51 // NOLINTEND(bugprone-assert-side-effect)
52 }
53
54 515 static size_t copy_count_nl(char *dst, const char *src, size_t len)
55 {
56 515 size_t nl = 0;
57
2/2
✓ Branch 6 → 3 taken 28801 times.
✓ Branch 6 → 7 taken 515 times.
29316 for (size_t i = 0; i < len; i++) {
58 28801 dst[i] = src[i];
59
2/2
✓ Branch 3 → 4 taken 239 times.
✓ Branch 3 → 5 taken 28562 times.
28801 if (src[i] == '\n') {
60 239 nl++;
61 }
62 }
63 515 return nl;
64 }
65
66 335 static size_t insert_to_current(BlockIter *cursor, StringView text)
67 {
68 335 Block *blk = cursor->blk;
69 335 size_t offset = cursor->offset;
70 335 size_t size = blk->size + text.length;
71 335 block_grow(blk, size);
72 335 memmove(blk->data + offset + text.length, blk->data + offset, blk->size - offset);
73 335 size_t nl = copy_count_nl(blk->data + offset, text.data, text.length);
74 335 blk->nl += nl;
75 335 blk->size = size;
76 335 return nl;
77 }
78
79 /*
80 * Combine current block and new data into smaller blocks:
81 *
82 * • Block _must_ contain whole lines
83 * • Block _must_ contain at least one line
84 * • Preferred maximum size of block is BLOCK_EDIT_SIZE
85 * • Size of any block can be larger than BLOCK_EDIT_SIZE only if there's
86 * a very long line
87 */
88 2 static size_t split_and_insert(BlockIter *cursor, StringView ins)
89 {
90 2 Block *blk = cursor->blk;
91 2 ListHead *prev_node = blk->node.prev;
92 2 const char *buf1 = blk->data;
93 2 const char *buf2 = ins.data;
94 2 const char *buf3 = blk->data + cursor->offset;
95 2 size_t size1 = cursor->offset;
96 2 size_t size2 = ins.length;
97 2 size_t size3 = blk->size - size1;
98 2 size_t total = size1 + size2 + size3;
99 2 size_t start = 0; // Beginning of new block
100 2 size_t size = 0; // Size of new block
101 2 size_t pos = 0; // Current position
102 2 size_t nl_added = 0;
103
104
2/2
✓ Branch 42 → 3 taken 28 times.
✓ Branch 42 → 43 taken 2 times.
30 while (start < total) {
105 // Size of new block if next line would be added
106 28 size_t new_size = 0;
107 28 size_t copied = 0;
108
109
2/2
✓ Branch 3 → 4 taken 5 times.
✓ Branch 3 → 6 taken 23 times.
28 if (pos < size1) {
110 5 const char *nl = memchr(buf1 + pos, '\n', size1 - pos);
111
2/2
✓ Branch 4 → 5 taken 4 times.
✓ Branch 4 → 6 taken 1 time.
5 if (nl) {
112 4 new_size = nl - buf1 + 1 - start;
113 }
114 }
115
116
3/4
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 11 taken 4 times.
✓ Branch 6 → 7 taken 20 times.
✓ Branch 6 → 11 taken 4 times.
28 if (!new_size && pos < size1 + size2) {
117 20 size_t offset = 0;
118
2/2
✓ Branch 7 → 8 taken 18 times.
✓ Branch 7 → 9 taken 2 times.
20 if (pos > size1) {
119 18 offset = pos - size1;
120 }
121
122 20 const char *nl = memchr(buf2 + offset, '\n', size2 - offset);
123
1/2
✓ Branch 9 → 10 taken 20 times.
✗ Branch 9 → 11 not taken.
20 if (nl) {
124 20 new_size = size1 + nl - buf2 + 1 - start;
125 }
126 }
127
128
2/2
✓ Branch 11 → 12 taken 4 times.
✓ Branch 11 → 17 taken 24 times.
28 if (!new_size && pos < total) {
129 4 size_t offset = 0;
130
2/2
✓ Branch 12 → 13 taken 2 times.
✓ Branch 12 → 14 taken 2 times.
4 if (pos > size1 + size2) {
131 2 offset = pos - size1 - size2;
132 }
133
134 4 const char *nl = memchr(buf3 + offset, '\n', size3 - offset);
135
1/2
✓ Branch 14 → 15 taken 4 times.
✗ Branch 14 → 16 not taken.
4 if (nl) {
136 4 new_size = size1 + size2 + nl - buf3 + 1 - start;
137 } else {
138 new_size = total - start;
139 }
140 }
141
142
2/2
✓ Branch 17 → 18 taken 25 times.
✓ Branch 17 → 20 taken 3 times.
28 if (new_size <= BLOCK_EDIT_SIZE) {
143 // Fits
144 25 size = new_size;
145 25 pos = start + new_size;
146
2/2
✓ Branch 18 → 19 taken 23 times.
✓ Branch 18 → 22 taken 2 times.
25 if (pos < total) {
147 23 continue;
148 }
149 } else {
150 // Does not fit
151
1/2
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 24 taken 3 times.
3 if (!size) {
152 // One block containing one very long line
153 size = new_size;
154 pos = start + new_size;
155 }
156 }
157
158 2 BUG_ON(!size);
159 5 Block *new = block_new(size);
160
2/2
✓ Branch 25 → 26 taken 1 time.
✓ Branch 25 → 28 taken 4 times.
5 if (start < size1) {
161 1 size_t avail = size1 - start;
162 1 size_t count = MIN(size, avail);
163 1 new->nl += copy_count_nl(new->data, buf1 + start, count);
164 1 copied += count;
165 1 start += count;
166 }
167
3/4
✓ Branch 28 → 29 taken 5 times.
✗ Branch 28 → 32 not taken.
✓ Branch 29 → 30 taken 4 times.
✓ Branch 29 → 32 taken 1 time.
5 if (start >= size1 && start < size1 + size2) {
168 4 size_t offset = start - size1;
169 4 size_t avail = size2 - offset;
170 4 size_t count = MIN(size - copied, avail);
171 4 new->nl += copy_count_nl(new->data + copied, buf2 + offset, count);
172 4 copied += count;
173 4 start += count;
174 }
175
2/2
✓ Branch 32 → 33 taken 3 times.
✓ Branch 32 → 37 taken 2 times.
5 if (start >= size1 + size2) {
176 3 size_t offset = start - size1 - size2;
177 3 size_t avail = size3 - offset;
178 3 size_t count = size - copied;
179 3 BUG_ON(count > avail);
180 3 new->nl += copy_count_nl(new->data + copied, buf3 + offset, count);
181 3 copied += count;
182 3 start += count;
183 }
184
185 5 new->size = size;
186 5 BUG_ON(copied != size);
187 5 list_insert_before(&new->node, &blk->node);
188
189 5 nl_added += new->nl;
190 5 size = 0;
191 }
192
193 2 cursor->blk = BLOCK(prev_node->next);
194
1/2
✗ Branch 45 → 44 not taken.
✓ Branch 45 → 46 taken 2 times.
2 while (cursor->offset > cursor->blk->size) {
195 cursor->offset -= cursor->blk->size;
196 cursor->blk = block_next(cursor->blk);
197 }
198
199 2 nl_added -= blk->nl;
200 2 block_free(blk);
201 2 return nl_added;
202 }
203
204 337 static size_t insert_bytes(BlockIter *cursor, StringView bytes)
205 {
206 // Blocks must contain whole lines.
207 // Last char of `bytes` might not be a newline.
208 337 block_iter_normalize(cursor);
209
210 337 Block *blk = cursor->blk;
211 337 size_t new_size = blk->size + bytes.length;
212
4/4
✓ Branch 3 → 4 taken 11 times.
✓ Branch 3 → 5 taken 326 times.
✓ Branch 4 → 5 taken 7 times.
✓ Branch 4 → 6 taken 4 times.
337 if (new_size <= blk->alloc || new_size <= BLOCK_EDIT_SIZE) {
213 333 return insert_to_current(cursor, bytes);
214 }
215
216
4/4
✓ Branch 6 → 7 taken 3 times.
✓ Branch 6 → 9 taken 1 time.
✓ Branch 7 → 8 taken 2 times.
✓ Branch 7 → 9 taken 1 time.
4 if (blk->nl <= 1 && !strview_memchr(bytes, '\n')) {
217 // Can't split this possibly very long line.
218 // insert_to_current() is much faster than split_and_insert().
219 2 return insert_to_current(cursor, bytes);
220 }
221
222 2 return split_and_insert(cursor, bytes);
223 }
224
225 337 void do_insert(View *view, StringView text)
226 {
227 337 Buffer *buffer = view->buffer;
228 337 size_t nl = insert_bytes(&view->cursor, text);
229 337 buffer->nl += nl;
230 337 sanity_check_blocks(view, true);
231
232 337 view_update_cursor_y(view);
233
2/2
✓ Branch 5 → 6 taken 213 times.
✓ Branch 5 → 7 taken 124 times.
337 buffer_mark_lines_changed(buffer, view->cy, nl ? LONG_MAX : view->cy);
234
2/2
✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 10 taken 335 times.
337 if (buffer->syntax) {
235 2 hl_insert(&buffer->line_start_states, view->cy, nl);
236 }
237 337 }
238
239 19 static bool only_block(const Buffer *buffer, const Block *blk)
240 {
241 19 const ListHead *head = &buffer->blocks;
242
2/4
✓ Branch 2 → 3 taken 19 times.
✗ Branch 2 → 5 not taken.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 19 times.
19 return !block_has_next(blk, head) && !block_has_prev(blk, head);
243 }
244
245 90 char *do_delete(View *view, size_t len, bool sanity_check_newlines)
246 {
247
1/2
✓ Branch 2 → 3 taken 90 times.
✗ Branch 2 → 34 not taken.
90 if (len == 0) {
248 return NULL;
249 }
250
251 90 ListHead *saved_prev_node = NULL;
252 90 Block *blk = view->cursor.blk;
253 90 size_t offset = view->cursor.offset;
254
2/2
✓ Branch 3 → 4 taken 39 times.
✓ Branch 3 → 5 taken 51 times.
90 if (!offset) {
255 // The block where the cursor is can become empty and thus
256 // may be deleted
257 39 saved_prev_node = blk->node.prev;
258 }
259
260 90 Buffer *buffer = view->buffer;
261 90 char *deleted = xmalloc(len);
262 90 size_t pos = 0;
263 90 size_t deleted_nl = 0;
264
265 90 while (pos < len) {
266 90 ListHead *next = blk->node.next;
267 90 size_t avail = blk->size - offset;
268 90 size_t count = MIN(len - pos, avail);
269 90 char *ptr = blk->data + offset;
270 90 size_t nl = copy_count_nl(deleted + pos, ptr, count);
271
2/2
✓ Branch 8 → 9 taken 63 times.
✓ Branch 8 → 10 taken 27 times.
90 if (count < avail) {
272 63 memmove(ptr, ptr + count, avail - count);
273 }
274
275 90 deleted_nl += nl;
276 90 buffer->nl -= nl;
277 90 blk->nl -= nl;
278 90 blk->size -= count;
279
3/4
✓ Branch 10 → 11 taken 19 times.
✓ Branch 10 → 13 taken 71 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 19 times.
90 if (!blk->size && !only_block(buffer, blk)) {
280 block_free(blk);
281 }
282
283 90 offset = 0;
284 90 pos += count;
285 90 blk = BLOCK(next);
286 270 BUG_ON(pos < len && next == &buffer->blocks);
287 }
288
289
2/2
✓ Branch 18 → 19 taken 39 times.
✓ Branch 18 → 22 taken 51 times.
90 if (saved_prev_node) {
290 // Cursor was at beginning of a block that was possibly deleted
291
1/2
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 21 taken 39 times.
39 if (saved_prev_node->next == &buffer->blocks) {
292 view->cursor.blk = BLOCK(saved_prev_node);
293 view->cursor.offset = view->cursor.blk->size;
294 } else {
295 39 view->cursor.blk = BLOCK(saved_prev_node->next);
296 }
297 }
298
299 90 blk = view->cursor.blk;
300
301 90 if (
302
2/2
✓ Branch 22 → 23 taken 71 times.
✓ Branch 22 → 27 taken 19 times.
90 blk->size
303
1/2
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 27 taken 71 times.
71 && blk->data[blk->size - 1] != '\n'
304 && block_has_next(blk, &buffer->blocks)
305 ) {
306 Block *next = block_next(blk);
307 size_t size = blk->size + next->size;
308 block_grow(blk, size);
309 memcpy(blk->data + blk->size, next->data, next->size);
310 blk->size = size;
311 blk->nl += next->nl;
312 block_free(next);
313 }
314
315 90 sanity_check_blocks(view, sanity_check_newlines);
316
317 90 view_update_cursor_y(view);
318
2/2
✓ Branch 29 → 30 taken 51 times.
✓ Branch 29 → 31 taken 39 times.
90 buffer_mark_lines_changed(buffer, view->cy, deleted_nl ? LONG_MAX : view->cy);
319
320
1/2
✗ Branch 32 → 33 not taken.
✓ Branch 32 → 34 taken 90 times.
90 if (buffer->syntax) {
321 hl_delete(&buffer->line_start_states, view->cy, deleted_nl);
322 }
323
324 return deleted;
325 }
326
327 46 char *do_replace(View *view, size_t del, StringView ins)
328 {
329 46 BUG_ON(del == 0);
330 46 BUG_ON(ins.length == 0);
331 46 block_iter_normalize(&view->cursor);
332
333 46 Block *blk = view->cursor.blk;
334 46 size_t offset = view->cursor.offset;
335 46 size_t avail = blk->size - offset;
336
2/2
✓ Branch 7 → 8 taken 5 times.
✓ Branch 7 → 9 taken 41 times.
46 if (del >= avail) {
337 5 goto slow;
338 }
339
340 41 size_t new_size = blk->size + ins.length - del;
341
1/2
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 13 taken 41 times.
41 if (new_size > BLOCK_EDIT_SIZE) {
342 // Should split
343 if (blk->nl > 1 || strview_memchr(ins, '\n')) {
344 // Most likely can be split
345 goto slow;
346 }
347 }
348
349 41 block_grow(blk, new_size);
350
351 // Modification is limited to one block
352 41 Buffer *buffer = view->buffer;
353 41 char *ptr = blk->data + offset;
354 41 char *deleted = xmalloc(del);
355 41 size_t del_nl = copy_count_nl(deleted, ptr, del);
356 41 blk->nl -= del_nl;
357 41 buffer->nl -= del_nl;
358
359
2/2
✓ Branch 16 → 17 taken 28 times.
✓ Branch 16 → 18 taken 13 times.
41 if (del != ins.length) {
360 28 memmove(ptr + ins.length, ptr + del, avail - del);
361 }
362
363 41 size_t ins_nl = copy_count_nl(ptr, ins.data, ins.length);
364 41 blk->nl += ins_nl;
365 41 buffer->nl += ins_nl;
366 41 blk->size = new_size;
367 41 sanity_check_blocks(view, true);
368 41 view_update_cursor_y(view);
369
370 // If the number of inserted and removed bytes are the same, some
371 // line(s) changed but the lines after them didn't move up or down
372
2/2
✓ Branch 21 → 22 taken 27 times.
✓ Branch 21 → 23 taken 14 times.
41 long max = (del_nl == ins_nl) ? view->cy + del_nl : LONG_MAX;
373 41 buffer_mark_lines_changed(buffer, view->cy, max);
374
375
1/2
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 32 taken 41 times.
41 if (buffer->syntax) {
376 hl_delete(&buffer->line_start_states, view->cy, del_nl);
377 hl_insert(&buffer->line_start_states, view->cy, ins_nl);
378 }
379
380 return deleted;
381
382 5 slow:
383 // The "sanity_check_newlines" argument of do_delete() is false here
384 // because it may be removing a terminating newline that do_insert()
385 // is going to insert again at a different position:
386 5 deleted = do_delete(view, del, false);
387 5 BUG_ON(!deleted); // do_delete() only returns NULL if len is 0
388 5 do_insert(view, ins);
389 5 return deleted;
390 }
391