dte test coverage


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