dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 92.0% 207 / 0 / 225
Functions: 100.0% 23 / 0 / 23
Branches: 80.2% 85 / 20 / 126

src/change.c
Line Branch Exec Source
1 #include <stdlib.h>
2 #include <string.h>
3 #include "change.h"
4 #include "buffer.h"
5 #include "command/error.h"
6 #include "edit.h"
7 #include "util/debug.h"
8 #include "util/xmalloc.h"
9
10 static struct {
11 ChangeMergeEnum merge;
12 ChangeMergeEnum prev_merge;
13 // This doesn't need to be local to Buffer, because commands are atomic
14 Change *barrier;
15 } cs; // NOLINT(*-avoid-non-const-global-variables)
16
17 345 static Change *alloc_change(void)
18 {
19 345 return xcalloc1(sizeof(Change));
20 }
21
22 342 static void add_change(Buffer *buffer, Change *change)
23 {
24 342 Change *head = buffer->cur_change;
25 342 change->next = head;
26 342 head->prev = xrenew(head->prev, head->nr_prev + 1);
27 342 head->prev[head->nr_prev++] = change;
28 342 buffer->cur_change = change;
29 342 }
30
31 56 static bool is_change_chain_barrier(const Change *change)
32 {
33
4/4
✓ Branch 2 → 3 taken 21 times.
✓ Branch 2 → 4 taken 35 times.
✓ Branch 3 → 4 taken 15 times.
✓ Branch 3 → 5 taken 6 times.
56 return !change->ins_count && !change->del_count;
34 }
35
36 280 static Change *new_change(Buffer *buffer)
37 {
38
2/2
✓ Branch 2 → 3 taken 31 times.
✓ Branch 2 → 5 taken 249 times.
280 if (cs.barrier) {
39 /*
40 * We are recording series of changes (:replace for example)
41 * and now we have just made the first change so we have to
42 * mark beginning of the chain.
43 *
44 * We could have done this before when starting the change
45 * chain but then we may have ended up with an empty chain.
46 * We don't want to record empty changes ever.
47 */
48 31 add_change(buffer, cs.barrier);
49 31 cs.barrier = NULL;
50 }
51
52 280 Change *change = alloc_change();
53 280 add_change(buffer, change);
54 280 return change;
55 }
56
57 280 static size_t buffer_offset(const View *view)
58 {
59 280 return block_iter_get_offset(&view->cursor);
60 }
61
62 274 static void record_insert(View *view, size_t len)
63 {
64 274 BUG_ON(!len);
65
4/4
✓ Branch 4 → 5 taken 221 times.
✓ Branch 4 → 9 taken 53 times.
✓ Branch 5 → 6 taken 88 times.
✓ Branch 5 → 9 taken 133 times.
274 if (cs.merge == cs.prev_merge && cs.merge == CHANGE_MERGE_INSERT) {
66 88 Change *change = view->buffer->cur_change;
67 88 BUG_ON(change->del_count);
68 88 change->ins_count += len;
69 88 return;
70 }
71
72 186 Change *change = new_change(view->buffer);
73 186 change->offset = buffer_offset(view);
74 186 change->ins_count = len;
75 }
76
77 54 static void record_delete(View *view, char *buf, size_t len, bool move_after)
78 {
79 54 BUG_ON(!len);
80 54 BUG_ON(!buf);
81 54 bool del = (cs.merge == CHANGE_MERGE_DELETE);
82 54 bool erase = (cs.merge == CHANGE_MERGE_ERASE);
83
84 // Consecutive DELETE or ERASE operations of the same type can be merged
85 // into the same Change entry. For matching DELETE operations, reallocate
86 // `change->buf`, then append and free `buf`. For ERASE, do likewise but
87 // with the arguments reversed.
88
4/4
✓ Branch 6 → 7 taken 43 times.
✓ Branch 6 → 16 taken 11 times.
✓ Branch 7 → 8 taken 2 times.
✓ Branch 7 → 16 taken 41 times.
54 if (cs.merge == cs.prev_merge && (del || erase)) {
89 2 Change *change = view->buffer->cur_change;
90
2/2
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 1 time.
2 char *left = del ? change->buf : buf; // Reallocated
91 2 char *right = del ? buf : change->buf; // Appended and freed
92
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 1 time.
2 size_t left_len = del ? change->del_count : len;
93 2 size_t right_len = del ? len : change->del_count;
94
1/2
✓ Branch 11 → 13 taken 1 time.
✗ Branch 11 → 14 not taken.
2 change->offset -= del ? 0 : len;
95
96 2 change->del_count += len;
97 2 change->buf = xrealloc(left, change->del_count);
98 2 memcpy(change->buf + left_len, right, right_len);
99 2 free(right);
100 2 return;
101 }
102
103 52 Change *change = new_change(view->buffer);
104 52 change->offset = buffer_offset(view);
105 52 change->del_count = len;
106 52 change->move_after = move_after;
107 52 change->buf = buf;
108 }
109
110 42 static void record_replace(View *view, char *deleted, size_t del_count, size_t ins_count)
111 {
112 42 BUG_ON(del_count && !deleted);
113 42 BUG_ON(!del_count && deleted);
114 42 BUG_ON(!del_count && !ins_count);
115
116 42 Change *change = new_change(view->buffer);
117 42 change->offset = buffer_offset(view);
118 42 change->ins_count = ins_count;
119 42 change->del_count = del_count;
120 42 change->buf = deleted;
121 42 }
122
123 8159 void begin_change(ChangeMergeEnum m)
124 {
125 8159 cs.merge = m;
126 8159 }
127
128 8145 void end_change(void)
129 {
130 8145 cs.prev_merge = cs.merge;
131 8145 }
132
133 34 void begin_change_chain(void)
134 {
135 34 BUG_ON(cs.barrier);
136
137 // Allocate change chain barrier but add it to the change tree only if
138 // there will be any real changes
139 34 cs.barrier = alloc_change();
140 34 cs.merge = CHANGE_MERGE_NONE;
141 34 }
142
143 34 void end_change_chain(View *view)
144 {
145
2/2
✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 4 taken 31 times.
34 if (cs.barrier) {
146 // There were no changes in this change chain
147 3 free(cs.barrier);
148 3 cs.barrier = NULL;
149 } else {
150 // There were some changes; add end of chain marker
151 31 add_change(view->buffer, alloc_change());
152 }
153 34 }
154
155 26 static void fix_cursors(const View *view, size_t offset, size_t del, size_t ins)
156 {
157 26 const Buffer *buffer = view->buffer;
158
2/2
✓ Branch 9 → 3 taken 52 times.
✓ Branch 9 → 10 taken 26 times.
78 for (size_t i = 0, n = buffer->views.count; i < n; i++) {
159 52 View *v = buffer->views.ptrs[i];
160
4/4
✓ Branch 3 → 4 taken 26 times.
✓ Branch 3 → 8 taken 26 times.
✓ Branch 4 → 5 taken 7 times.
✓ Branch 4 → 8 taken 19 times.
52 if (v != view && offset < v->saved_cursor_offset) {
161
1/2
✓ Branch 5 → 6 taken 7 times.
✗ Branch 5 → 7 not taken.
7 if (offset + del <= v->saved_cursor_offset) {
162 7 v->saved_cursor_offset -= del;
163 7 v->saved_cursor_offset += ins;
164 } else {
165 v->saved_cursor_offset = offset;
166 }
167 }
168 }
169 26 }
170
171 50 static void reverse_change(View *view, Change *change)
172 {
173 50 const size_t ins_count = change->ins_count;
174 50 const size_t del_count = change->del_count;
175 50 BUG_ON(!del_count && !ins_count);
176
177
2/2
✓ Branch 4 → 5 taken 26 times.
✓ Branch 4 → 6 taken 24 times.
50 if (view->buffer->views.count > 1) {
178 // NOLINTNEXTLINE(readability-suspicious-call-argument)
179 26 fix_cursors(view, change->offset, ins_count, del_count);
180 }
181
182 50 block_iter_goto_offset(&view->cursor, change->offset);
183
184
2/2
✓ Branch 7 → 8 taken 15 times.
✓ Branch 7 → 12 taken 35 times.
50 if (ins_count == 0) {
185 // Convert delete to insert
186 15 do_insert(view, string_view(change->buf, del_count));
187
2/2
✓ Branch 9 → 10 taken 4 times.
✓ Branch 9 → 11 taken 11 times.
15 if (change->move_after) {
188 4 block_iter_skip_bytes(&view->cursor, del_count);
189 }
190 15 change->ins_count = del_count;
191 15 change->del_count = 0;
192 15 free(change->buf);
193 15 change->buf = NULL;
194 15 return;
195 }
196
197
2/2
✓ Branch 12 → 13 taken 31 times.
✓ Branch 12 → 15 taken 4 times.
35 if (del_count == 0) {
198 // Convert insert to delete
199 31 change->buf = do_delete(view, ins_count, true);
200 31 change->del_count = ins_count;
201 31 change->ins_count = 0;
202 31 return;
203 }
204
205 // Reverse replace
206 // NOLINTNEXTLINE(readability-suspicious-call-argument)
207 4 char *buf = do_replace(view, ins_count, string_view(change->buf, del_count));
208 4 free(change->buf);
209 4 change->buf = buf;
210 4 change->ins_count = del_count;
211 4 change->del_count = ins_count;
212 }
213
214 1024 bool undo(View *view, ErrorBuffer *ebuf)
215 {
216 1024 Change *change = view->buffer->cur_change;
217 1024 view_reset_preferred_x(view);
218
2/2
✓ Branch 3 → 4 taken 47 times.
✓ Branch 3 → 12 taken 977 times.
1024 if (!change->next) {
219 return false;
220 }
221
222
2/2
✓ Branch 4 → 5 taken 3 times.
✓ Branch 4 → 10 taken 44 times.
47 if (is_change_chain_barrier(change)) {
223 unsigned long count = 0;
224 9 while (1) {
225 6 change = change->next;
226
2/2
✓ Branch 5 → 6 taken 3 times.
✓ Branch 5 → 8 taken 3 times.
6 if (is_change_chain_barrier(change)) {
227 break;
228 }
229 3 reverse_change(view, change);
230 3 count++;
231 }
232
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 11 taken 3 times.
3 if (count > 1) {
233 info_msg(ebuf, "Undid %lu changes", count);
234 }
235 } else {
236 44 reverse_change(view, change);
237 }
238
239 47 view->buffer->cur_change = change->next;
240 47 return true;
241 }
242
243 6 bool redo(View *view, ErrorBuffer *ebuf, unsigned long change_id)
244 {
245 6 Change *change = view->buffer->cur_change;
246 6 view_reset_preferred_x(view);
247
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 6 taken 5 times.
6 if (!change->prev) {
248 // Don't complain if change_id is 0
249
1/2
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 23 not taken.
1 if (change_id) {
250 1 error_msg(ebuf, "Nothing to redo");
251 }
252 return false;
253 }
254
255 5 const unsigned long nr_prev = change->nr_prev;
256 5 BUG_ON(nr_prev == 0);
257
2/2
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 11 taken 4 times.
5 if (change_id == 0) {
258 // Default to newest change
259 1 change_id = nr_prev - 1;
260
1/2
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 15 taken 1 time.
1 if (nr_prev > 1) {
261 unsigned long i = change_id + 1;
262 info_msg(ebuf, "Redoing newest (%lu) of %lu possible changes", i, nr_prev);
263 }
264 } else {
265
2/2
✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 15 taken 2 times.
4 if (--change_id >= nr_prev) {
266
2/2
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 14 taken 1 time.
2 if (nr_prev == 1) {
267 1 return error_msg(ebuf, "There is only 1 possible change to redo");
268 }
269 1 return error_msg(ebuf, "There are only %lu possible changes to redo", nr_prev);
270 }
271 }
272
273 3 change = change->prev[change_id];
274
1/2
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 21 taken 3 times.
3 if (is_change_chain_barrier(change)) {
275 unsigned long count = 0;
276 while (1) {
277 change = change->prev[change->nr_prev - 1];
278 if (is_change_chain_barrier(change)) {
279 break;
280 }
281 reverse_change(view, change);
282 count++;
283 }
284 if (count > 1) {
285 info_msg(ebuf, "Redid %lu changes", count);
286 }
287 } else {
288 3 reverse_change(view, change);
289 }
290
291 3 view->buffer->cur_change = change;
292 3 return true;
293 }
294
295 86 void free_changes(Change *c)
296 {
297 99 top:
298
2/2
✓ Branch 5 → 4 taken 342 times.
✓ Branch 5 → 6 taken 99 times.
441 while (c->nr_prev) {
299 342 c = c->prev[c->nr_prev - 1];
300 }
301
302 // c is leaf now
303
2/2
✓ Branch 10 → 7 taken 342 times.
✓ Branch 10 → 11 taken 86 times.
428 while (c->next) {
304 342 Change *next = c->next;
305 342 free(c->buf);
306 342 free(c);
307
308 342 c = next;
309
2/2
✓ Branch 7 → 8 taken 13 times.
✓ Branch 7 → 9 taken 329 times.
342 if (--c->nr_prev) {
310 13 goto top;
311 }
312
313 // We have become leaf
314 329 free(c->prev);
315 }
316 86 }
317
318 274 void buffer_insert_bytes(View *view, StringView bytes)
319 {
320 274 view_reset_preferred_x(view);
321
1/2
✓ Branch 3 → 4 taken 274 times.
✗ Branch 3 → 14 not taken.
274 if (bytes.length == 0) {
322 return;
323 }
324
325 274 size_t rec_len = bytes.length;
326
4/4
✓ Branch 5 → 6 taken 225 times.
✓ Branch 5 → 9 taken 49 times.
✓ Branch 6 → 7 taken 42 times.
✓ Branch 6 → 9 taken 183 times.
274 if (!strview_has_suffix(bytes, "\n") && block_iter_is_eof(&view->cursor)) {
327 // Force newline at EOF
328 42 do_insert(view, strview("\n"));
329 42 rec_len++;
330 }
331
332 274 do_insert(view, bytes);
333 274 record_insert(view, rec_len);
334
335
1/2
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 14 taken 274 times.
274 if (view->buffer->views.count > 1) {
336 fix_cursors(view, block_iter_get_offset(&view->cursor), bytes.length, 0);
337 }
338 }
339
340 98 static bool would_delete_last_bytes(BlockIter bi, size_t del_count)
341 {
342 98 while (1) {
343 98 size_t avail = bi.blk->size - bi.offset;
344
2/2
✓ Branch 3 → 4 taken 7 times.
✓ Branch 3 → 7 taken 91 times.
98 if (avail > del_count) {
345 return false;
346 }
347
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 7 times.
7 if (!block_iter_next_block(&bi)) {
348 return true;
349 }
350 del_count -= avail;
351 }
352 }
353
354 57 static void buffer_delete_bytes_internal(View *view, size_t len, bool move_after)
355 {
356 57 view_reset_preferred_x(view);
357
2/2
✓ Branch 3 → 4 taken 56 times.
✓ Branch 3 → 18 taken 1 time.
57 if (len == 0) {
358 return;
359 }
360
361 // Check if all newlines from EOF would be deleted
362
2/2
✓ Branch 5 → 6 taken 3 times.
✓ Branch 5 → 13 taken 53 times.
56 if (would_delete_last_bytes(view->cursor, len)) {
363 3 BlockIter bi = view->cursor;
364 3 CodePoint u;
365
3/4
✓ Branch 7 → 8 taken 3 times.
✗ Branch 7 → 12 not taken.
✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 12 taken 1 time.
3 if (block_iter_prev_char(&bi, &u) && u != '\n') {
366 // No newline before cursor
367
1/2
✓ Branch 9 → 10 taken 2 times.
✗ Branch 9 → 12 not taken.
2 if (--len == 0) {
368 2 begin_change(CHANGE_MERGE_NONE);
369 2 return;
370 }
371 }
372 }
373
374 54 record_delete(view, do_delete(view, len, true), len, move_after);
375
376
1/2
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 18 taken 54 times.
54 if (view->buffer->views.count > 1) {
377 fix_cursors(view, block_iter_get_offset(&view->cursor), len, 0);
378 }
379 }
380
381 48 void buffer_delete_bytes(View *view, size_t len)
382 {
383 48 buffer_delete_bytes_internal(view, len, false);
384 48 }
385
386 9 void buffer_erase_bytes(View *view, size_t len)
387 {
388 9 buffer_delete_bytes_internal(view, len, true);
389 9 }
390
391 286 void buffer_replace_bytes(View *view, size_t del_count, StringView ins)
392 {
393
2/2
✓ Branch 2 → 3 taken 236 times.
✓ Branch 2 → 5 taken 50 times.
286 if (del_count == 0) {
394 236 buffer_insert_bytes(view, ins);
395 236 return;
396 }
397
2/2
✓ Branch 5 → 6 taken 8 times.
✓ Branch 5 → 8 taken 42 times.
50 if (ins.length == 0) {
398 8 buffer_delete_bytes(view, del_count);
399 8 return;
400 }
401
402 42 view_reset_preferred_x(view);
403
404 // Check if all newlines from EOF would be deleted
405
2/2
✓ Branch 10 → 11 taken 4 times.
✓ Branch 10 → 16 taken 38 times.
42 if (would_delete_last_bytes(view->cursor, del_count)) {
406
1/2
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 16 taken 4 times.
4 if (!strview_has_suffix(ins, "\n")) {
407 // Don't replace last newline
408 if (--del_count == 0) {
409 buffer_insert_bytes(view, ins);
410 return;
411 }
412 }
413 }
414
415 42 char *deleted = do_replace(view, del_count, ins);
416 42 record_replace(view, deleted, del_count, ins.length);
417
418
1/2
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 21 taken 42 times.
42 if (view->buffer->views.count > 1) {
419 fix_cursors(view, block_iter_get_offset(&view->cursor), del_count, ins.length);
420 }
421 }
422