dte test coverage


Directory: ./
File: src/change.c
Date: 2025-02-14 16:55:22
Exec Total Coverage
Lines: 215 236 91.1%
Functions: 23 23 100.0%
Branches: 83 102 81.4%

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