dte test coverage


Directory: ./
File: src/frame.c
Date: 2025-06-04 06:50:24
Exec Total Coverage
Lines: 251 294 85.4%
Functions: 31 32 96.9%
Branches: 72 124 58.1%

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