dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 85.5% 253 / 1 / 297
Functions: 96.9% 31 / 0 / 32
Branches: 58.1% 72 / 20 / 144

src/frame.c
Line Branch Exec Source
1 #include "frame.h"
2 #include "editor.h"
3 #include "util/xmalloc.h"
4 #include "window.h"
5
6 enum {
7 WINDOW_MIN_WIDTH = 8,
8 WINDOW_MIN_HEIGHT = 3,
9 };
10
11 // Recursion is bounded by the number of descendant frames, which is
12 // typically not more than 5 or so
13 // NOLINTBEGIN(misc-no-recursion)
14
15 11 static void sanity_check_frame(const Frame *frame)
16 {
17 11 size_t nframes = frame->frames.count;
18 11 bool has_window = !!frame->window;
19 11 bool has_frames = !!nframes;
20
21
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 6 taken 11 times.
11 if (has_window == has_frames || nframes == 1) {
22 BUG (
23 "frames must contain a single window or multiple subframes; got "
24 "frames=%zu, window=%s",
25 nframes, has_window ? "yes" : "no"
26 );
27 }
28
29 11 BUG_ON(has_window && frame != frame->window->frame);
30 11 }
31
32 46 static int get_min_w(const Frame *frame)
33 {
34
2/2
✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 11 taken 43 times.
46 if (frame->window) {
35 return WINDOW_MIN_WIDTH;
36 }
37
38 3 void **subframes = frame->frames.ptrs;
39 3 size_t count = frame->frames.count;
40
1/2
✓ Branch 3 → 4 taken 3 times.
✗ Branch 3 → 10 not taken.
3 if (!frame->vertical) {
41 3 int w = count - 1; // Separators
42
2/2
✓ Branch 7 → 5 taken 6 times.
✓ Branch 7 → 11 taken 3 times.
9 for (size_t i = 0; i < count; i++) {
43 6 w += get_min_w(subframes[i]);
44 }
45 return w;
46 }
47
48 int max = 0;
49 for (size_t i = 0; i < count; i++) {
50 int w = get_min_w(subframes[i]);
51 max = MAX(w, max);
52 }
53 return max;
54 }
55
56 30 static int get_min_h(const Frame *frame)
57 {
58
2/2
✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 10 taken 27 times.
30 if (frame->window) {
59 return WINDOW_MIN_HEIGHT;
60 }
61
62 3 void **subframes = frame->frames.ptrs;
63 3 size_t count = frame->frames.count;
64
1/2
✗ Branch 3 → 6 not taken.
✓ Branch 3 → 9 taken 3 times.
3 if (frame->vertical) {
65 int h = 0;
66 for (size_t i = 0; i < count; i++) {
67 h += get_min_h(subframes[i]);
68 }
69 return h;
70 }
71
72 int max = 0;
73
2/2
✓ Branch 9 → 7 taken 6 times.
✓ Branch 9 → 10 taken 3 times.
9 for (size_t i = 0; i < count; i++) {
74 6 int h = get_min_h(subframes[i]);
75 6 max = MAX(h, max);
76 }
77 return max;
78 }
79
80 // Get parent frame and assert non-NULL, for use in contexts where
81 // `frame` may not be the root frame
82 62 static Frame *frame_must_get_parent(const Frame *frame)
83 {
84 62 Frame *parent = frame->parent;
85 62 BUG_ON(!parent);
86 62 return parent;
87 }
88
89 16 static int get_min(const Frame *frame)
90 {
91 16 const Frame *parent = frame_must_get_parent(frame);
92
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 16 times.
16 return parent->vertical ? get_min_h(frame) : get_min_w(frame);
93 }
94
95 13 static int get_size(const Frame *frame)
96 {
97 13 const Frame *parent = frame_must_get_parent(frame);
98
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 13 times.
13 return parent->vertical ? frame->h : frame->w;
99 }
100
101 4 static int get_container_size(const Frame *frame)
102 {
103
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 4 times.
4 return frame->vertical ? frame->h : frame->w;
104 }
105
106 18 static void set_size(Frame *frame, int size)
107 {
108 18 const Frame *parent = frame_must_get_parent(frame);
109 18 bool vertical = parent->vertical;
110
1/2
✓ Branch 3 → 4 taken 18 times.
✗ Branch 3 → 5 not taken.
18 int w = vertical ? parent->w : size;
111 18 int h = vertical ? size : parent->h;
112 18 frame_set_size(frame, w, h);
113 18 }
114
115 4 static void divide_equally(const Frame *frame)
116 {
117 4 void **ptrs = frame->frames.ptrs;
118 4 size_t count = frame->frames.count;
119 4 BUG_ON(count == 0);
120 4 BUG_ON(!ptrs);
121
122 4 int *min = xmallocarray(count, sizeof(int));
123
2/2
✓ Branch 10 → 8 taken 8 times.
✓ Branch 10 → 11 taken 4 times.
16 for (size_t i = 0; i < count; i++) {
124 8 min[i] = get_min(ptrs[i]);
125 }
126
127 4 int *size = xcalloc(count, sizeof(int));
128 4 int s = get_container_size(frame);
129 4 int q, r, used;
130 4 size_t n = count;
131
132 // Consume q and r as equally as possible
133 4 do {
134 4 used = 0;
135 4 q = s / n;
136 4 r = s % n;
137
2/2
✓ Branch 18 → 14 taken 8 times.
✓ Branch 18 → 19 taken 4 times.
12 for (size_t i = 0; i < count; i++) {
138
2/4
✓ Branch 14 → 15 taken 8 times.
✗ Branch 14 → 17 not taken.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 8 times.
8 if (size[i] == 0 && min[i] > q) {
139 size[i] = min[i];
140 used += min[i];
141 n--;
142 }
143 }
144 4 s -= used;
145
1/2
✗ Branch 19 → 13 not taken.
✓ Branch 19 → 24 taken 4 times.
4 } while (used && n > 0);
146
147
2/2
✓ Branch 24 → 20 taken 8 times.
✓ Branch 24 → 25 taken 4 times.
12 for (size_t i = 0; i < count; i++) {
148 8 Frame *c = ptrs[i];
149
1/2
✓ Branch 20 → 21 taken 8 times.
✗ Branch 20 → 22 not taken.
8 if (size[i] == 0) {
150 8 size[i] = q + (r-- > 0);
151 }
152 8 set_size(c, size[i]);
153 }
154
155 4 free(size);
156 4 free(min);
157 4 }
158
159 static void fix_size(const Frame *frame)
160 {
161 void **ptrs = frame->frames.ptrs;
162 size_t count = frame->frames.count;
163 int *size = xmallocarray(count, sizeof(int));
164 int *min = xmallocarray(count, sizeof(int));
165 int total = 0;
166
167 for (size_t i = 0; i < count; i++) {
168 const Frame *c = ptrs[i];
169 min[i] = get_min(c);
170 size[i] = MAX(get_size(c), min[i]);
171 total += size[i];
172 }
173
174 int s = get_container_size(frame);
175 if (total > s) {
176 int n = total - s;
177 for (ssize_t i = count - 1; n > 0 && i >= 0; i--) {
178 int new_size = MAX(size[i] - n, min[i]);
179 n -= size[i] - new_size;
180 size[i] = new_size;
181 }
182 } else {
183 size[count - 1] += s - total;
184 }
185
186 for (size_t i = 0; i < count; i++) {
187 set_size(ptrs[i], size[i]);
188 }
189
190 free(size);
191 free(min);
192 }
193
194 2 static void add_to_sibling_size(Frame *frame, int count)
195 {
196 2 const Frame *parent = frame_must_get_parent(frame);
197 2 const PointerArray *pframes = &parent->frames;
198 2 size_t idx = ptr_array_xindex(pframes, frame);
199 2 bool last = (idx == pframes->count - 1);
200
1/2
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 6 not taken.
2 frame = pframes->ptrs[last ? idx - 1 : idx + 1];
201 2 set_size(frame, get_size(frame) + count);
202 2 }
203
204 3 static int sub(Frame *frame, int count)
205 {
206 3 int min = get_min(frame);
207 3 int old = get_size(frame);
208 3 int new = MAX(min, old - count);
209
1/2
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 6 not taken.
3 if (new != old) {
210 3 set_size(frame, new);
211 }
212 3 return count - (old - new);
213 }
214
215 3 static void subtract_from_sibling_size(const Frame *frame, int count)
216 {
217 3 const Frame *parent = frame_must_get_parent(frame);
218 3 const PointerArray *pframes = &parent->frames;
219 3 size_t idx = ptr_array_xindex(pframes, frame);
220 3 void **ptrs = pframes->ptrs;
221
222
1/2
✗ Branch 8 → 5 not taken.
✓ Branch 8 → 11 taken 3 times.
3 for (size_t i = idx + 1, n = pframes->count; i < n; i++) {
223 count = sub(ptrs[i], count);
224 if (count == 0) {
225 return;
226 }
227 }
228
229
1/2
✓ Branch 11 → 9 taken 3 times.
✗ Branch 11 → 12 not taken.
3 for (size_t i = idx; i > 0; i--) {
230 3 count = sub(ptrs[i - 1], count);
231
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 3 times.
3 if (count == 0) {
232 return;
233 }
234 }
235 }
236
237 5 static void resize_to(Frame *frame, int size)
238 {
239 5 const Frame *parent = frame_must_get_parent(frame);
240 5 size_t count = parent->frames.count;
241 5 BUG_ON(count == 0);
242
243
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 5 times.
5 int total = parent->vertical ? parent->h : parent->w;
244 5 int min = get_min(frame);
245 5 int max = total - ((count - 1) * min);
246 5 max = MAX(min, max);
247 5 size = CLAMP(size, min, max);
248
249 5 int change = size - get_size(frame);
250
1/2
✓ Branch 10 → 11 taken 5 times.
✗ Branch 10 → 15 not taken.
5 if (change == 0) {
251 return;
252 }
253
254 5 set_size(frame, size);
255
2/2
✓ Branch 12 → 13 taken 2 times.
✓ Branch 12 → 14 taken 3 times.
5 if (change < 0) {
256 2 add_to_sibling_size(frame, -change);
257 } else {
258 3 subtract_from_sibling_size(frame, change);
259 }
260 }
261
262 30 static bool rightmost_frame(const Frame *frame)
263 {
264 30 const Frame *parent = frame->parent;
265
2/2
✓ Branch 2 → 3 taken 18 times.
✓ Branch 2 → 6 taken 12 times.
30 if (!parent) {
266 return true;
267 }
268
1/2
✓ Branch 3 → 4 taken 18 times.
✗ Branch 3 → 5 not taken.
18 if (!parent->vertical) {
269
2/2
✓ Branch 4 → 5 taken 9 times.
✓ Branch 4 → 6 taken 9 times.
18 if (frame != parent->frames.ptrs[parent->frames.count - 1]) {
270 return false;
271 }
272 }
273 9 return rightmost_frame(parent);
274 }
275
276 16 static Frame *new_frame(void)
277 {
278 16 Frame *frame = xcalloc1(sizeof(*frame));
279 16 frame->equal_size = true;
280 16 return frame;
281 }
282
283 15 static Frame *add_frame(Frame *parent, Window *window, size_t idx)
284 {
285 15 Frame *frame = new_frame();
286 15 frame->parent = parent;
287 15 frame->window = window;
288 15 window->frame = frame;
289
2/2
✓ Branch 3 → 4 taken 5 times.
✓ Branch 3 → 8 taken 10 times.
15 if (parent) {
290 5 BUG_ON(idx > parent->frames.count);
291 5 ptr_array_insert(&parent->frames, frame, idx);
292 5 parent->window = NULL;
293 }
294 15 return frame;
295 }
296
297 10 Frame *new_root_frame(Window *window)
298 {
299 10 return add_frame(NULL, window, 0);
300 }
301
302 6 static Frame *find_resizable(Frame *frame, ResizeDirection dir)
303 {
304
2/2
✓ Branch 2 → 8 taken 2 times.
✓ Branch 2 → 9 taken 4 times.
6 if (dir == RESIZE_DIRECTION_AUTO) {
305 return frame;
306 }
307
308
2/2
✓ Branch 8 → 3 taken 2 times.
✓ Branch 8 → 9 taken 1 time.
3 while (frame->parent) {
309
3/4
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 1 time.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 9 not taken.
2 if (dir == RESIZE_DIRECTION_VERTICAL && frame->parent->vertical) {
310 return frame;
311 }
312
3/4
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 1 time.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 9 taken 1 time.
2 if (dir == RESIZE_DIRECTION_HORIZONTAL && !frame->parent->vertical) {
313 return frame;
314 }
315 1 frame = frame->parent;
316 }
317 return NULL;
318 }
319
320 24 void frame_set_size(Frame *frame, int w, int h)
321 {
322 24 int min_w = get_min_w(frame);
323 24 int min_h = get_min_h(frame);
324 24 w = MAX(w, min_w);
325 24 h = MAX(h, min_h);
326 24 frame->w = w;
327 24 frame->h = h;
328
329
2/2
✓ Branch 4 → 5 taken 21 times.
✓ Branch 4 → 8 taken 3 times.
24 if (frame->window) {
330 21 w -= rightmost_frame(frame) ? 0 : 1; // Separator
331 21 window_set_size(frame->window, w, h);
332 21 return;
333 }
334
335
1/2
✓ Branch 8 → 9 taken 3 times.
✗ Branch 8 → 10 not taken.
3 if (frame->equal_size) {
336 3 divide_equally(frame);
337 } else {
338 fix_size(frame);
339 }
340 }
341
342 1 void frame_equalize_sizes(Frame *parent)
343 {
344 1 parent->equal_size = true;
345 1 divide_equally(parent);
346 1 update_window_coordinates(parent);
347 1 }
348
349 6 void frame_resize(Frame *frame, ResizeDirection dir, int size)
350 {
351 6 frame = find_resizable(frame, dir);
352
2/2
✓ Branch 3 → 4 taken 5 times.
✓ Branch 3 → 7 taken 1 time.
6 if (!frame) {
353 return;
354 }
355
356 5 Frame *parent = frame_must_get_parent(frame);
357 5 parent->equal_size = false;
358 5 resize_to(frame, size);
359 5 update_window_coordinates(parent);
360 }
361
362 3 void frame_add_to_size(Frame *frame, ResizeDirection dir, int amount)
363 {
364 3 frame_resize(frame, dir, get_size(frame) + amount);
365 3 }
366
367 29 static void update_frame_coordinates(const Frame *frame, int x, int y)
368 {
369
2/2
✓ Branch 2 → 3 taken 20 times.
✓ Branch 2 → 5 taken 9 times.
29 if (frame->window) {
370 20 window_set_coordinates(frame->window, x, y);
371 20 return;
372 }
373
374 9 void **ptrs = frame->frames.ptrs;
375
2/2
✓ Branch 11 → 6 taken 18 times.
✓ Branch 11 → 12 taken 9 times.
27 for (size_t i = 0, n = frame->frames.count; i < n; i++) {
376 18 const Frame *c = ptrs[i];
377 18 update_frame_coordinates(c, x, y);
378
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 18 times.
18 if (frame->vertical) {
379 y += c->h;
380 } else {
381 18 x += c->w;
382 }
383 }
384 }
385
386 11 static Frame *get_root_frame(Frame *frame)
387 {
388 11 BUG_ON(!frame);
389
1/2
✗ Branch 4 → 4 not taken.
✓ Branch 4 → 5 taken 11 times.
11 while (frame->parent) {
390 frame = frame->parent;
391 }
392 11 return frame;
393 }
394
395 11 void update_window_coordinates(Frame *frame)
396 {
397 11 update_frame_coordinates(get_root_frame(frame), 0, 0);
398 11 }
399
400 2 Frame *frame_split(Window *window, bool vertical, bool before)
401 {
402 2 Frame *frame = window->frame;
403 2 Frame *parent = frame->parent;
404
1/4
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 2 times.
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 5 not taken.
2 if (!parent || parent->vertical != vertical) {
405 // Reparent window
406 2 frame->vertical = vertical;
407 2 add_frame(frame, window, 0);
408 2 parent = frame;
409 }
410
411 2 size_t idx = ptr_array_xindex(&parent->frames, window->frame);
412 2 idx += before ? 0 : 1;
413 2 frame = add_frame(parent, new_window(window->editor), idx);
414 2 parent->equal_size = true;
415
416 // Recalculate
417 2 frame_set_size(parent, parent->w, parent->h);
418 2 update_window_coordinates(parent);
419 2 return frame;
420 }
421
422 // Doesn't really split root but adds new frame between root and its contents
423 1 Frame *frame_split_root(EditorState *e, bool vertical, bool before)
424 {
425 1 Frame *old_root = e->root_frame;
426 1 Frame *new_root = new_frame();
427 1 ptr_array_append(&new_root->frames, old_root);
428 1 old_root->parent = new_root;
429 1 new_root->vertical = vertical;
430 1 e->root_frame = new_root;
431
432 1 Frame *frame = add_frame(new_root, new_window(e), before ? 0 : 1);
433 1 frame_set_size(new_root, old_root->w, old_root->h);
434 1 update_window_coordinates(new_root);
435 1 return frame;
436 }
437
438 // NOTE: does not remove frame from frame->parent->frames
439 14 static void free_frame(Frame *frame)
440 {
441 14 frame->parent = NULL;
442 14 ptr_array_free_cb(&frame->frames, FREE_FUNC(free_frame));
443
444
2/2
✓ Branch 3 → 4 taken 13 times.
✓ Branch 3 → 6 taken 1 time.
14 if (frame->window) {
445 13 window_free(frame->window);
446 13 frame->window = NULL;
447 }
448
449 14 free(frame);
450 14 }
451
452 12 void frame_remove(EditorState *e, Frame *frame)
453 {
454 12 Frame *parent = frame->parent;
455
2/2
✓ Branch 2 → 3 taken 10 times.
✓ Branch 2 → 5 taken 2 times.
12 if (!parent) {
456 10 free_frame(frame);
457 10 return;
458 }
459
460 2 ptr_array_remove(&parent->frames, frame);
461 2 free_frame(frame);
462
463
1/2
✓ Branch 7 → 8 taken 2 times.
✗ Branch 7 → 14 not taken.
2 if (parent->frames.count == 1) {
464 // Replace parent with the only child frame
465 2 Frame *gp = parent->parent;
466 2 Frame *c = parent->frames.ptrs[0];
467 2 c->parent = gp;
468 2 c->w = parent->w;
469 2 c->h = parent->h;
470
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 11 taken 2 times.
2 if (gp) {
471 size_t idx = ptr_array_xindex(&gp->frames, parent);
472 gp->frames.ptrs[idx] = c;
473 } else {
474 2 e->root_frame = c;
475 }
476 2 ptr_array_free_array(&parent->frames);
477 2 free(parent);
478 2 parent = c;
479 }
480
481 // Recalculate
482 2 frame_set_size(parent, parent->w, parent->h);
483 2 update_window_coordinates(parent);
484 }
485
486 1 void dump_frame(const Frame *frame, size_t level, String *str)
487 {
488 1 sanity_check_frame(frame);
489 1 string_append_memset(str, ' ', level * 4);
490 1 string_sprintf(str, "%dx%d", frame->w, frame->h);
491
492 1 const Window *w = frame->window;
493
1/2
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 11 not taken.
1 if (w) {
494 1 const char *name = buffer_filename(w->view->buffer);
495 1 string_append_byte(str, '\n');
496 1 string_append_memset(str, ' ', (level + 1) * 4);
497 1 string_sprintf(str, "%d,%d %dx%d %s\n", w->x, w->y, w->w, w->h, name);
498 1 return;
499 }
500
501 string_append_cstring(str, frame->vertical ? " V" : " H");
502 string_append_cstring(str, frame->equal_size ? "\n" : " !\n");
503
504 for (size_t i = 0, n = frame->frames.count; i < n; i++) {
505 const Frame *c = frame->frames.ptrs[i];
506 dump_frame(c, level + 1, str);
507 }
508 }
509
510 #if DEBUG_ASSERTIONS_ENABLED
511 10 void frame_debug(const Frame *frame)
512 {
513 10 sanity_check_frame(frame);
514
2/2
✓ Branch 8 → 4 taken 4 times.
✓ Branch 8 → 9 taken 10 times.
14 for (size_t i = 0, n = frame->frames.count; i < n; i++) {
515 4 const Frame *c = frame->frames.ptrs[i];
516 4 BUG_ON(c->parent != frame);
517 4 frame_debug(c);
518 }
519 10 }
520 #endif
521
522 // NOLINTEND(misc-no-recursion)
523