Coverage Report

Created: 2026-05-30 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/layout.c
Line
Count
Source
1
/* $OpenBSD$ */
2
3
/*
4
 * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
5
 * Copyright (c) 2016 Stephen Kent <smkent@smkent.net>
6
 *
7
 * Permission to use, copy, modify, and distribute this software for any
8
 * purpose with or without fee is hereby granted, provided that the above
9
 * copyright notice and this permission notice appear in all copies.
10
 *
11
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
16
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
17
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18
 */
19
20
#include <sys/types.h>
21
22
#include <stdlib.h>
23
24
#include "tmux.h"
25
26
/*
27
 * The window layout is a tree of cells each of which can be one of: a
28
 * left-right container for a list of cells, a top-bottom container for a list
29
 * of cells, or a container for a window pane.
30
 *
31
 * Each window has a pointer to the root of its layout tree (containing its
32
 * panes), every pane has a pointer back to the cell containing it, and each
33
 * cell a pointer to its parent cell.
34
 */
35
36
static u_int  layout_resize_check(struct window *, struct layout_cell *,
37
        enum layout_type);
38
static int  layout_resize_pane_grow(struct window *, struct layout_cell *,
39
        enum layout_type, int, int);
40
static int  layout_resize_pane_shrink(struct window *, struct layout_cell *,
41
        enum layout_type, int);
42
static u_int  layout_new_pane_size(struct window *, u_int,
43
        struct layout_cell *, enum layout_type, u_int, u_int,
44
        u_int);
45
static int  layout_set_size_check(struct window *, struct layout_cell *,
46
        enum layout_type, int);
47
static void layout_resize_child_cells(struct window *,
48
        struct layout_cell *);
49
void    layout_redistribute_cells(struct window *, struct layout_cell *,
50
        enum layout_type);
51
52
/* Create a new layout cell. */
53
struct layout_cell *
54
layout_create_cell(struct layout_cell *lcparent)
55
0
{
56
0
  struct layout_cell  *lc;
57
58
0
  lc = xmalloc(sizeof *lc);
59
0
  lc->type = LAYOUT_WINDOWPANE;
60
0
  lc->parent = lcparent;
61
62
0
  TAILQ_INIT(&lc->cells);
63
64
0
  lc->sx = UINT_MAX;
65
0
  lc->sy = UINT_MAX;
66
67
0
  lc->xoff = INT_MAX;
68
0
  lc->yoff = INT_MAX;
69
70
0
  lc->wp = NULL;
71
72
0
  return (lc);
73
0
}
74
75
/* Free a layout cell. */
76
void
77
layout_free_cell(struct layout_cell *lc)
78
23.0k
{
79
23.0k
  struct layout_cell  *lcchild;
80
81
23.0k
  if (lc == NULL)
82
23.0k
    return;
83
84
0
  switch (lc->type) {
85
0
  case LAYOUT_LEFTRIGHT:
86
0
  case LAYOUT_TOPBOTTOM:
87
0
    while (!TAILQ_EMPTY(&lc->cells)) {
88
0
      lcchild = TAILQ_FIRST(&lc->cells);
89
0
      TAILQ_REMOVE(&lc->cells, lcchild, entry);
90
0
      layout_free_cell(lcchild);
91
0
    }
92
0
    break;
93
0
  case LAYOUT_FLOATING:
94
    /*
95
     * A floating layout cell is only used temporarily while
96
     * select-layout constructs a layout. Remove the children from
97
     * the temporary layout, then free temporary floating layout
98
     * cell. Each floating pane has stub layout.
99
     */
100
0
    while (!TAILQ_EMPTY(&lc->cells)) {
101
0
      lcchild = TAILQ_FIRST(&lc->cells);
102
0
      TAILQ_REMOVE(&lc->cells, lcchild, entry);
103
0
      lcchild->parent = NULL;
104
0
    }
105
0
    break;
106
0
  case LAYOUT_WINDOWPANE:
107
0
    if (lc->wp != NULL) {
108
0
      lc->wp->layout_cell->parent = NULL;
109
0
      lc->wp->layout_cell = NULL;
110
0
    }
111
0
    break;
112
0
  }
113
114
0
  free(lc);
115
0
}
116
117
/* Log a cell. */
118
void
119
layout_print_cell(struct layout_cell *lc, const char *hdr, u_int n)
120
0
{
121
0
  struct layout_cell  *lcchild;
122
0
  const char    *type;
123
124
0
  if (lc == NULL)
125
0
    return;
126
127
0
  switch (lc->type) {
128
0
  case LAYOUT_LEFTRIGHT:
129
0
    type = "LEFTRIGHT";
130
0
    break;
131
0
  case LAYOUT_TOPBOTTOM:
132
0
    type = "TOPBOTTOM";
133
0
    break;
134
0
  case LAYOUT_FLOATING:
135
0
    type = "FLOATING";
136
0
    break;
137
0
  case LAYOUT_WINDOWPANE:
138
0
    type = "WINDOWPANE";
139
0
    break;
140
0
  default:
141
0
    type = "UNKNOWN";
142
0
    break;
143
0
  }
144
0
  log_debug("%s:%*s%p type %s [parent %p] wp=%p [%d,%d %ux%u]", hdr, n,
145
0
      " ", lc, type, lc->parent, lc->wp, lc->xoff, lc->yoff, lc->sx,
146
0
      lc->sy);
147
0
  switch (lc->type) {
148
0
  case LAYOUT_LEFTRIGHT:
149
0
  case LAYOUT_TOPBOTTOM:
150
0
  case LAYOUT_FLOATING:
151
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry)
152
0
      layout_print_cell(lcchild, hdr, n + 1);
153
0
    break;
154
0
  case LAYOUT_WINDOWPANE:
155
0
    break;
156
0
  }
157
0
}
158
159
/* Search for a cell by the border position. */
160
struct layout_cell *
161
layout_search_by_border(struct layout_cell *lc, u_int x, u_int y)
162
0
{
163
0
  struct layout_cell  *lcchild, *last = NULL;
164
165
0
  TAILQ_FOREACH(lcchild, &lc->cells, entry) {
166
0
    if ((int)x >= lcchild->xoff &&
167
0
        (int)x < lcchild->xoff + (int)lcchild->sx &&
168
0
        (int)y >= lcchild->yoff &&
169
0
        (int)y < lcchild->yoff + (int)lcchild->sy) {
170
      /* Inside the cell - recurse. */
171
0
      return (layout_search_by_border(lcchild, x, y));
172
0
    }
173
174
0
    if (last == NULL) {
175
0
      last = lcchild;
176
0
      continue;
177
0
    }
178
179
0
    switch (lc->type) {
180
0
    case LAYOUT_LEFTRIGHT:
181
0
      if ((int)x < lcchild->xoff &&
182
0
          (int)x >= last->xoff + (int)last->sx)
183
0
        return (last);
184
0
      break;
185
0
    case LAYOUT_TOPBOTTOM:
186
0
      if ((int)y < lcchild->yoff &&
187
0
          (int)y >= last->yoff + (int)last->sy)
188
0
        return (last);
189
0
      break;
190
0
    case LAYOUT_WINDOWPANE:
191
0
    case LAYOUT_FLOATING:
192
0
      break;
193
0
    }
194
195
0
    last = lcchild;
196
0
  }
197
198
0
  return (NULL);
199
0
}
200
201
/* Set cell size. */
202
void
203
layout_set_size(struct layout_cell *lc, u_int sx, u_int sy, int xoff, int yoff)
204
0
{
205
0
  lc->sx = sx;
206
0
  lc->sy = sy;
207
208
0
  lc->xoff = xoff;
209
0
  lc->yoff = yoff;
210
0
}
211
212
/* Make a cell a leaf cell. */
213
void
214
layout_make_leaf(struct layout_cell *lc, struct window_pane *wp)
215
0
{
216
0
  lc->type = LAYOUT_WINDOWPANE;
217
218
0
  TAILQ_INIT(&lc->cells);
219
220
0
  wp->layout_cell = lc;
221
0
  lc->wp = wp;
222
0
}
223
224
/* Make a cell a node cell. */
225
void
226
layout_make_node(struct layout_cell *lc, enum layout_type type)
227
0
{
228
0
  if (type == LAYOUT_WINDOWPANE)
229
0
    fatalx("bad layout type");
230
0
  lc->type = type;
231
232
0
  TAILQ_INIT(&lc->cells);
233
234
0
  if (lc->wp != NULL)
235
0
    lc->wp->layout_cell = NULL;
236
0
  lc->wp = NULL;
237
0
}
238
239
/* Fix z-indexes. */
240
void
241
layout_fix_zindexes(struct window *w, struct layout_cell *lc)
242
0
{
243
0
  struct layout_cell  *lcchild;
244
245
0
  if (lc == NULL)
246
0
    return;
247
248
0
  switch (lc->type) {
249
0
  case LAYOUT_WINDOWPANE:
250
0
    TAILQ_INSERT_TAIL(&w->z_index, lc->wp, zentry);
251
0
    break;
252
0
  case LAYOUT_LEFTRIGHT:
253
0
  case LAYOUT_TOPBOTTOM:
254
0
  case LAYOUT_FLOATING:
255
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry)
256
0
      layout_fix_zindexes(w, lcchild);
257
0
    return;
258
0
  default:
259
0
    fatalx("bad layout type");
260
0
  }
261
0
}
262
263
/* Fix cell offsets for a child cell. */
264
static void
265
layout_fix_offsets1(struct layout_cell *lc)
266
0
{
267
0
  struct layout_cell  *lcchild;
268
0
  int      xoff, yoff;
269
270
0
  if (lc->type == LAYOUT_LEFTRIGHT) {
271
0
    xoff = lc->xoff;
272
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry) {
273
0
      lcchild->xoff = xoff;
274
0
      lcchild->yoff = lc->yoff;
275
0
      if (lcchild->type != LAYOUT_WINDOWPANE)
276
0
        layout_fix_offsets1(lcchild);
277
0
      xoff += lcchild->sx + 1;
278
0
    }
279
0
  } else {
280
0
    yoff = lc->yoff;
281
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry) {
282
0
      lcchild->xoff = lc->xoff;
283
0
      lcchild->yoff = yoff;
284
0
      if (lcchild->type != LAYOUT_WINDOWPANE)
285
0
        layout_fix_offsets1(lcchild);
286
0
      yoff += lcchild->sy + 1;
287
0
    }
288
0
  }
289
0
}
290
291
/* Update cell offsets based on their sizes. */
292
void
293
layout_fix_offsets(struct window *w)
294
0
{
295
0
  struct layout_cell  *lc = w->layout_root;
296
297
0
  lc->xoff = 0;
298
0
  lc->yoff = 0;
299
300
0
  layout_fix_offsets1(lc);
301
0
}
302
303
/* Is this a top cell? */
304
static int
305
layout_cell_is_top(struct window *w, struct layout_cell *lc)
306
0
{
307
0
  struct layout_cell  *next;
308
309
0
  while (lc != w->layout_root) {
310
0
    next = lc->parent;
311
0
    if (next == NULL)
312
0
      return (0);
313
0
    if (next->type == LAYOUT_TOPBOTTOM &&
314
0
        lc != TAILQ_FIRST(&next->cells))
315
0
      return (0);
316
0
    lc = next;
317
0
  }
318
0
  return (1);
319
0
}
320
321
/* Is this a bottom cell? */
322
static int
323
layout_cell_is_bottom(struct window *w, struct layout_cell *lc)
324
0
{
325
0
  struct layout_cell  *next;
326
327
0
  while (lc != w->layout_root) {
328
0
    next = lc->parent;
329
0
    if (next == NULL)
330
0
      return (0);
331
0
    if (next->type == LAYOUT_TOPBOTTOM &&
332
0
        lc != TAILQ_LAST(&next->cells, layout_cells))
333
0
      return (0);
334
0
    lc = next;
335
0
  }
336
0
  return (1);
337
0
}
338
339
/*
340
 * Returns 1 if we need to add an extra line for the pane status line. This is
341
 * the case for the most upper or lower panes only.
342
 */
343
static int
344
layout_add_horizontal_border(struct window *w, struct layout_cell *lc,
345
    int status)
346
0
{
347
0
  if (status == PANE_STATUS_TOP)
348
0
    return (layout_cell_is_top(w, lc));
349
0
  if (status == PANE_STATUS_BOTTOM)
350
0
    return (layout_cell_is_bottom(w, lc));
351
0
  return (0);
352
0
}
353
354
/* Update pane offsets and sizes based on their cells. */
355
void
356
layout_fix_panes(struct window *w, struct window_pane *skip)
357
1.59k
{
358
1.59k
  struct window_pane  *wp;
359
1.59k
  struct layout_cell  *lc;
360
1.59k
  int      status, scrollbars, sb_pos, sb_w, sb_pad;
361
1.59k
  u_int      sx, sy;
362
363
1.59k
  status = options_get_number(w->options, "pane-border-status");
364
1.59k
  scrollbars = options_get_number(w->options, "pane-scrollbars");
365
1.59k
  sb_pos = options_get_number(w->options, "pane-scrollbars-position");
366
367
1.59k
  TAILQ_FOREACH(wp, &w->panes, entry) {
368
1.59k
    if ((lc = wp->layout_cell) == NULL || wp == skip)
369
1.59k
      continue;
370
371
0
    wp->xoff = lc->xoff;
372
0
    wp->yoff = lc->yoff;
373
0
    sx = lc->sx;
374
0
    sy = lc->sy;
375
376
0
    if ((~wp->flags & PANE_FLOATING) &&
377
0
        layout_add_horizontal_border(w, lc, status)) {
378
0
      if (status == PANE_STATUS_TOP)
379
0
        wp->yoff++;
380
0
      sy--;
381
0
    }
382
383
0
    if (window_pane_show_scrollbar(wp, scrollbars)) {
384
0
      sb_w = wp->scrollbar_style.width;
385
0
      sb_pad = wp->scrollbar_style.pad;
386
0
      if (sb_w < 1)
387
0
        sb_w = 1;
388
0
      if (sb_pad < 0)
389
0
        sb_pad = 0;
390
0
      if (sb_pos == PANE_SCROLLBARS_LEFT) {
391
0
        if ((int)sx - sb_w < PANE_MINIMUM) {
392
0
          wp->xoff = wp->xoff +
393
0
              (int)sx - PANE_MINIMUM;
394
0
          sx = PANE_MINIMUM;
395
0
        } else {
396
0
          sx = sx - sb_w - sb_pad;
397
0
          wp->xoff = wp->xoff + sb_w + sb_pad;
398
0
        }
399
0
      } else /* sb_pos == PANE_SCROLLBARS_RIGHT */
400
0
        if ((int)sx - sb_w - sb_pad < PANE_MINIMUM)
401
0
          sx = PANE_MINIMUM;
402
0
        else
403
0
          sx = sx - sb_w - sb_pad;
404
0
      wp->flags |= PANE_REDRAWSCROLLBAR;
405
0
    }
406
407
0
    window_pane_resize(wp, sx, sy);
408
0
  }
409
1.59k
}
410
411
/* Count the number of available cells in a layout. */
412
u_int
413
layout_count_cells(struct layout_cell *lc)
414
0
{
415
0
  struct layout_cell  *lcchild;
416
0
  u_int      count = 0;
417
418
0
  switch (lc->type) {
419
0
  case LAYOUT_WINDOWPANE:
420
0
    return (1);
421
0
  case LAYOUT_LEFTRIGHT:
422
0
  case LAYOUT_TOPBOTTOM:
423
0
  case LAYOUT_FLOATING:
424
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry)
425
0
      count += layout_count_cells(lcchild);
426
0
    return (count);
427
0
  default:
428
0
    fatalx("bad layout type");
429
0
  }
430
0
}
431
432
/* Calculate how much size is available to be removed from a cell. */
433
static u_int
434
layout_resize_check(struct window *w, struct layout_cell *lc,
435
    enum layout_type type)
436
0
{
437
0
  struct layout_cell  *lcchild;
438
0
  struct style    *sb_style = &w->active->scrollbar_style;
439
0
  u_int      available, minimum;
440
0
  int      status, scrollbars;
441
442
0
  status = options_get_number(w->options, "pane-border-status");
443
0
  scrollbars = options_get_number(w->options, "pane-scrollbars");
444
445
0
  if (lc->type == LAYOUT_WINDOWPANE) {
446
    /* Space available in this cell only. */
447
0
    if (type == LAYOUT_LEFTRIGHT) {
448
0
      available = lc->sx;
449
0
      if (scrollbars)
450
0
        minimum = PANE_MINIMUM + sb_style->width +
451
0
            sb_style->pad;
452
0
      else
453
0
        minimum = PANE_MINIMUM;
454
0
    } else {
455
0
      available = lc->sy;
456
0
      if (layout_add_horizontal_border(w, lc, status))
457
0
        minimum = PANE_MINIMUM + 1;
458
0
      else
459
0
        minimum = PANE_MINIMUM;
460
0
    }
461
0
    if (available > minimum)
462
0
      available -= minimum;
463
0
    else
464
0
      available = 0;
465
0
  } else if (lc->type == type) {
466
    /* Same type: total of available space in all child cells. */
467
0
    available = 0;
468
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry)
469
0
      available += layout_resize_check(w, lcchild, type);
470
0
  } else {
471
    /* Different type: minimum of available space in child cells. */
472
0
    minimum = UINT_MAX;
473
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry) {
474
0
      available = layout_resize_check(w, lcchild, type);
475
0
      if (available < minimum)
476
0
        minimum = available;
477
0
    }
478
0
    available = minimum;
479
0
  }
480
481
0
  return (available);
482
0
}
483
484
/*
485
 * Adjust cell size evenly, including altering its children. This function
486
 * expects the change to have already been bounded to the space available.
487
 */
488
void
489
layout_resize_adjust(struct window *w, struct layout_cell *lc,
490
    enum layout_type type, int change)
491
0
{
492
0
  struct layout_cell  *lcchild;
493
494
  /* Adjust the cell size. */
495
0
  if (type == LAYOUT_LEFTRIGHT)
496
0
    lc->sx += change;
497
0
  else
498
0
    lc->sy += change;
499
500
  /* If this is a leaf cell, that is all that is necessary. */
501
0
  if (type == LAYOUT_WINDOWPANE)
502
0
    return;
503
504
  /* Child cell runs in a different direction. */
505
0
  if (lc->type != type) {
506
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry)
507
0
      layout_resize_adjust(w, lcchild, type, change);
508
0
    return;
509
0
  }
510
511
  /*
512
   * Child cell runs in the same direction. Adjust each child equally
513
   * until no further change is possible.
514
   */
515
0
  while (change != 0) {
516
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry) {
517
0
      if (change == 0)
518
0
        break;
519
0
      if (change > 0) {
520
0
        layout_resize_adjust(w, lcchild, type, 1);
521
0
        change--;
522
0
        continue;
523
0
      }
524
0
      if (layout_resize_check(w, lcchild, type) > 0) {
525
0
        layout_resize_adjust(w, lcchild, type, -1);
526
0
        change++;
527
0
      }
528
0
    }
529
0
  }
530
0
}
531
532
/* Destroy a cell and redistribute the space. */
533
void
534
layout_destroy_cell(struct window *w, struct layout_cell *lc,
535
    struct layout_cell **lcroot)
536
0
{
537
0
  struct layout_cell     *lcother, *lcparent;
538
539
  /*
540
   * If no parent, this is either a floating pane or the last
541
   * pane so window close is imminent and there is no need to
542
   * resize anything.
543
   */
544
0
  lcparent = lc->parent;
545
0
  if (lcparent == NULL) {
546
0
    if (lc->wp != NULL && ~lc->wp->flags & PANE_FLOATING)
547
0
      *lcroot = NULL;
548
0
    layout_free_cell(lc);
549
0
    return;
550
0
  }
551
552
  /* A floating cell need only be removed from the parent. */
553
0
  if (lcparent->type == LAYOUT_FLOATING) {
554
0
    TAILQ_REMOVE(&lcparent->cells, lc, entry);
555
0
    layout_free_cell(lc);
556
0
    return;
557
0
  }
558
559
  /* Merge the space into the previous or next cell. */
560
0
  if (lc == TAILQ_FIRST(&lcparent->cells))
561
0
    lcother = TAILQ_NEXT(lc, entry);
562
0
  else
563
0
    lcother = TAILQ_PREV(lc, layout_cells, entry);
564
0
  if (lcother != NULL && lcparent->type == LAYOUT_LEFTRIGHT)
565
0
    layout_resize_adjust(w, lcother, lcparent->type, lc->sx + 1);
566
0
  else if (lcother != NULL)
567
0
    layout_resize_adjust(w, lcother, lcparent->type, lc->sy + 1);
568
569
  /* Remove this from the parent's list. */
570
0
  TAILQ_REMOVE(&lcparent->cells, lc, entry);
571
0
  layout_free_cell(lc);
572
573
  /*
574
   * If the parent now has one cell, remove the parent from the tree and
575
   * replace it by that cell.
576
   */
577
0
  lc = TAILQ_FIRST(&lcparent->cells);
578
0
  if (lc != NULL && TAILQ_NEXT(lc, entry) == NULL) {
579
0
    TAILQ_REMOVE(&lcparent->cells, lc, entry);
580
581
0
    lc->parent = lcparent->parent;
582
0
    if (lc->parent == NULL) {
583
0
      lc->xoff = 0;
584
0
      lc->yoff = 0;
585
0
      *lcroot = lc;
586
0
    } else
587
0
      TAILQ_REPLACE(&lc->parent->cells, lcparent, lc, entry);
588
589
0
    layout_free_cell(lcparent);
590
0
  }
591
0
}
592
593
/* Initialize layout for pane. */
594
void
595
layout_init(struct window *w, struct window_pane *wp)
596
0
{
597
0
  struct layout_cell  *lc;
598
599
0
  lc = w->layout_root = layout_create_cell(NULL);
600
0
  layout_set_size(lc, w->sx, w->sy, 0, 0);
601
0
  layout_make_leaf(lc, wp);
602
0
  layout_fix_panes(w, NULL);
603
0
}
604
605
/* Free layout for pane. */
606
void
607
layout_free(struct window *w)
608
0
{
609
0
  layout_free_cell(w->layout_root);
610
0
}
611
612
/* Resize the entire layout after window resize. */
613
void
614
layout_resize(struct window *w, u_int sx, u_int sy)
615
0
{
616
0
  struct layout_cell  *lc = w->layout_root;
617
0
  int      xlimit, ylimit, xchange, ychange;
618
619
  /*
620
   * Adjust horizontally. Do not attempt to reduce the layout lower than
621
   * the minimum (more than the amount returned by layout_resize_check).
622
   *
623
   * This can mean that the window size is smaller than the total layout
624
   * size: redrawing this is handled at a higher level, but it does leave
625
   * a problem with growing the window size here: if the current size is
626
   * < the minimum, growing proportionately by adding to each pane is
627
   * wrong as it would keep the layout size larger than the window size.
628
   * Instead, spread the difference between the minimum and the new size
629
   * out proportionately - this should leave the layout fitting the new
630
   * window size.
631
   */
632
0
  xchange = sx - lc->sx;
633
0
  xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT);
634
0
  if (xchange < 0 && xchange < -xlimit)
635
0
    xchange = -xlimit;
636
0
  if (xlimit == 0) {
637
0
    if (sx <= lc->sx) /* lc->sx is minimum possible */
638
0
      xchange = 0;
639
0
    else
640
0
      xchange = sx - lc->sx;
641
0
  }
642
0
  if (xchange != 0)
643
0
    layout_resize_adjust(w, lc, LAYOUT_LEFTRIGHT, xchange);
644
645
  /* Adjust vertically in a similar fashion. */
646
0
  ychange = sy - lc->sy;
647
0
  ylimit = layout_resize_check(w, lc, LAYOUT_TOPBOTTOM);
648
0
  if (ychange < 0 && ychange < -ylimit)
649
0
    ychange = -ylimit;
650
0
  if (ylimit == 0) {
651
0
    if (sy <= lc->sy) /* lc->sy is minimum possible */
652
0
      ychange = 0;
653
0
    else
654
0
      ychange = sy - lc->sy;
655
0
  }
656
0
  if (ychange != 0)
657
0
    layout_resize_adjust(w, lc, LAYOUT_TOPBOTTOM, ychange);
658
659
  /* Fix cell offsets. */
660
0
  layout_fix_offsets(w);
661
0
  layout_fix_panes(w, NULL);
662
0
}
663
664
/* Resize a pane to an absolute size. */
665
void
666
layout_resize_pane_to(struct window_pane *wp, enum layout_type type,
667
    u_int new_size)
668
0
{
669
0
  struct layout_cell     *lc, *lcparent;
670
0
  int     change, size;
671
672
0
  lc = wp->layout_cell;
673
674
  /* Find next parent of the same type. */
675
0
  lcparent = lc->parent;
676
0
  while (lcparent != NULL && lcparent->type != type) {
677
0
    lc = lcparent;
678
0
    lcparent = lc->parent;
679
0
  }
680
0
  if (lcparent == NULL)
681
0
    return;
682
683
  /* Work out the size adjustment. */
684
0
  if (type == LAYOUT_LEFTRIGHT)
685
0
    size = lc->sx;
686
0
  else
687
0
    size = lc->sy;
688
0
  if (lc == TAILQ_LAST(&lcparent->cells, layout_cells))
689
0
    change = size - new_size;
690
0
  else
691
0
    change = new_size - size;
692
693
  /* Resize the pane. */
694
0
  layout_resize_pane(wp, type, change, 1);
695
0
}
696
697
void
698
layout_resize_layout(struct window *w, struct layout_cell *lc,
699
    enum layout_type type, int change, int opposite)
700
0
{
701
0
  int needed, size;
702
703
  /* Grow or shrink the cell. */
704
0
  needed = change;
705
0
  while (needed != 0) {
706
0
    if (change > 0) {
707
0
      size = layout_resize_pane_grow(w, lc, type, needed,
708
0
          opposite);
709
0
      needed -= size;
710
0
    } else {
711
0
      size = layout_resize_pane_shrink(w, lc, type, needed);
712
0
      needed += size;
713
0
    }
714
715
0
    if (size == 0) /* no more change possible */
716
0
      break;
717
0
  }
718
719
  /* Fix cell offsets. */
720
0
  layout_fix_offsets(w);
721
0
  layout_fix_panes(w, NULL);
722
0
  notify_window("window-layout-changed", w);
723
0
}
724
725
/* Resize a single pane within the layout. */
726
void
727
layout_resize_pane(struct window_pane *wp, enum layout_type type, int change,
728
    int opposite)
729
0
{
730
0
  struct layout_cell  *lc, *lcparent;
731
732
0
  lc = wp->layout_cell;
733
734
  /* Find next parent of the same type. */
735
0
  lcparent = lc->parent;
736
0
  while (lcparent != NULL && lcparent->type != type) {
737
0
    lc = lcparent;
738
0
    lcparent = lc->parent;
739
0
  }
740
0
  if (lcparent == NULL)
741
0
    return;
742
743
  /* If this is the last cell, move back one. */
744
0
  if (lc == TAILQ_LAST(&lcparent->cells, layout_cells))
745
0
    lc = TAILQ_PREV(lc, layout_cells, entry);
746
747
0
  layout_resize_layout(wp->window, lc, type, change, opposite);
748
0
}
749
750
/* Helper function to grow pane. */
751
static int
752
layout_resize_pane_grow(struct window *w, struct layout_cell *lc,
753
    enum layout_type type, int needed, int opposite)
754
0
{
755
0
  struct layout_cell  *lcadd, *lcremove;
756
0
  u_int      size = 0;
757
758
  /* Growing. Always add to the current cell. */
759
0
  lcadd = lc;
760
761
  /* Look towards the tail for a suitable cell for reduction. */
762
0
  lcremove = TAILQ_NEXT(lc, entry);
763
0
  while (lcremove != NULL) {
764
0
    size = layout_resize_check(w, lcremove, type);
765
0
    if (size > 0)
766
0
      break;
767
0
    lcremove = TAILQ_NEXT(lcremove, entry);
768
0
  }
769
770
  /* If none found, look towards the head. */
771
0
  if (opposite && lcremove == NULL) {
772
0
    lcremove = TAILQ_PREV(lc, layout_cells, entry);
773
0
    while (lcremove != NULL) {
774
0
      size = layout_resize_check(w, lcremove, type);
775
0
      if (size > 0)
776
0
        break;
777
0
      lcremove = TAILQ_PREV(lcremove, layout_cells, entry);
778
0
    }
779
0
  }
780
0
  if (lcremove == NULL)
781
0
    return (0);
782
783
  /* Change the cells. */
784
0
  if (size > (u_int) needed)
785
0
    size = needed;
786
0
  layout_resize_adjust(w, lcadd, type, size);
787
0
  layout_resize_adjust(w, lcremove, type, -size);
788
0
  return (size);
789
0
}
790
791
/* Helper function to shrink pane. */
792
static int
793
layout_resize_pane_shrink(struct window *w, struct layout_cell *lc,
794
    enum layout_type type, int needed)
795
0
{
796
0
  struct layout_cell  *lcadd, *lcremove;
797
0
  u_int      size;
798
799
  /* Shrinking. Find cell to remove from by walking towards head. */
800
0
  lcremove = lc;
801
0
  do {
802
0
    size = layout_resize_check(w, lcremove, type);
803
0
    if (size != 0)
804
0
      break;
805
0
    lcremove = TAILQ_PREV(lcremove, layout_cells, entry);
806
0
  } while (lcremove != NULL);
807
0
  if (lcremove == NULL)
808
0
    return (0);
809
810
  /* And add onto the next cell (from the original cell). */
811
0
  lcadd = TAILQ_NEXT(lc, entry);
812
0
  if (lcadd == NULL)
813
0
    return (0);
814
815
  /* Change the cells. */
816
0
  if (size > (u_int) -needed)
817
0
    size = -needed;
818
0
  layout_resize_adjust(w, lcadd, type, size);
819
0
  layout_resize_adjust(w, lcremove, type, -size);
820
0
  return (size);
821
0
}
822
823
/* Assign window pane to new cell. */
824
void
825
layout_assign_pane(struct layout_cell *lc, struct window_pane *wp,
826
    int do_not_resize)
827
0
{
828
0
  layout_make_leaf(lc, wp);
829
0
  if (do_not_resize)
830
0
    layout_fix_panes(wp->window, wp);
831
0
  else
832
0
    layout_fix_panes(wp->window, NULL);
833
0
}
834
835
/* Calculate the new pane size for resized parent. */
836
static u_int
837
layout_new_pane_size(struct window *w, u_int previous, struct layout_cell *lc,
838
    enum layout_type type, u_int size, u_int count_left, u_int size_left)
839
0
{
840
0
  u_int new_size, min, max, available;
841
842
  /* If this is the last cell, it can take all of the remaining size. */
843
0
  if (count_left == 1)
844
0
    return (size_left);
845
846
  /* How much is available in this parent? */
847
0
  available = layout_resize_check(w, lc, type);
848
849
  /*
850
   * Work out the minimum size of this cell and the new size
851
   * proportionate to the previous size.
852
   */
853
0
  min = (PANE_MINIMUM + 1) * (count_left - 1);
854
0
  if (type == LAYOUT_LEFTRIGHT) {
855
0
    if (lc->sx - available > min)
856
0
      min = lc->sx - available;
857
0
    new_size = (lc->sx * size) / previous;
858
0
  } else {
859
0
    if (lc->sy - available > min)
860
0
      min = lc->sy - available;
861
0
    new_size = (lc->sy * size) / previous;
862
0
  }
863
864
  /* Check against the maximum and minimum size. */
865
0
  max = size_left - min;
866
0
  if (new_size > max)
867
0
    new_size = max;
868
0
  if (new_size < PANE_MINIMUM)
869
0
    new_size = PANE_MINIMUM;
870
0
  return (new_size);
871
0
}
872
873
/* Check if the cell and all its children can be resized to a specific size. */
874
static int
875
layout_set_size_check(struct window *w, struct layout_cell *lc,
876
    enum layout_type type, int size)
877
0
{
878
0
  struct layout_cell  *lcchild;
879
0
  u_int      new_size, available, previous, count, idx;
880
881
  /* Cells with no children must just be bigger than minimum. */
882
0
  if (lc->type == LAYOUT_WINDOWPANE)
883
0
    return (size >= PANE_MINIMUM);
884
0
  available = size;
885
886
  /* Count number of children. */
887
0
  count = 0;
888
0
  TAILQ_FOREACH(lcchild, &lc->cells, entry)
889
0
    count++;
890
891
  /* Check new size will work for each child. */
892
0
  if (lc->type == type) {
893
0
    if (available < (count * 2) - 1)
894
0
      return (0);
895
896
0
    if (type == LAYOUT_LEFTRIGHT)
897
0
      previous = lc->sx;
898
0
    else
899
0
      previous = lc->sy;
900
901
0
    idx = 0;
902
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry) {
903
0
      new_size = layout_new_pane_size(w, previous, lcchild,
904
0
          type, size, count - idx, available);
905
0
      if (idx == count - 1) {
906
0
        if (new_size > available)
907
0
          return (0);
908
0
        available -= new_size;
909
0
      } else {
910
0
        if (new_size + 1 > available)
911
0
          return (0);
912
0
        available -= new_size + 1;
913
0
      }
914
0
      if (!layout_set_size_check(w, lcchild, type, new_size))
915
0
        return (0);
916
0
      idx++;
917
0
    }
918
0
  } else {
919
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry) {
920
0
      if (lcchild->type == LAYOUT_WINDOWPANE)
921
0
        continue;
922
0
      if (!layout_set_size_check(w, lcchild, type, size))
923
0
        return (0);
924
0
    }
925
0
  }
926
927
0
  return (1);
928
0
}
929
930
/* Resize all child cells to fit within the current cell. */
931
static void
932
layout_resize_child_cells(struct window *w, struct layout_cell *lc)
933
0
{
934
0
  struct layout_cell  *lcchild;
935
0
  u_int      previous, available, count, idx;
936
937
0
  if (lc->type == LAYOUT_WINDOWPANE)
938
0
    return;
939
940
  /* What is the current size used? */
941
0
  count = 0;
942
0
  previous = 0;
943
0
  TAILQ_FOREACH(lcchild, &lc->cells, entry) {
944
0
    count++;
945
0
    if (lc->type == LAYOUT_LEFTRIGHT)
946
0
      previous += lcchild->sx;
947
0
    else if (lc->type == LAYOUT_TOPBOTTOM)
948
0
      previous += lcchild->sy;
949
0
  }
950
0
  previous += (count - 1);
951
952
  /* And how much is available? */
953
0
  available = 0;
954
0
  if (lc->type == LAYOUT_LEFTRIGHT)
955
0
    available = lc->sx;
956
0
  else if (lc->type == LAYOUT_TOPBOTTOM)
957
0
    available = lc->sy;
958
959
  /* Resize children into the new size. */
960
0
  idx = 0;
961
0
  TAILQ_FOREACH(lcchild, &lc->cells, entry) {
962
0
    if (lc->type == LAYOUT_TOPBOTTOM) {
963
0
      lcchild->sx = lc->sx;
964
0
      lcchild->xoff = lc->xoff;
965
0
    } else {
966
0
      lcchild->sx = layout_new_pane_size(w, previous, lcchild,
967
0
          lc->type, lc->sx, count - idx, available);
968
0
      available -= (lcchild->sx + 1);
969
0
    }
970
0
    if (lc->type == LAYOUT_LEFTRIGHT)
971
0
      lcchild->sy = lc->sy;
972
0
    else {
973
0
      lcchild->sy = layout_new_pane_size(w, previous, lcchild,
974
0
          lc->type, lc->sy, count - idx, available);
975
0
      available -= (lcchild->sy + 1);
976
0
    }
977
0
    layout_resize_child_cells(w, lcchild);
978
0
    idx++;
979
0
  }
980
0
}
981
982
/*
983
 * Split a pane into two. size is a hint, or -1 for default half/half
984
 * split. This must be followed by layout_assign_pane before much else happens!
985
 */
986
struct layout_cell *
987
layout_split_pane(struct window_pane *wp, enum layout_type type, int size,
988
    int flags)
989
0
{
990
0
  struct layout_cell  *lc, *lcparent, *lcnew, *lc1, *lc2;
991
0
  struct style    *sb_style = &wp->scrollbar_style;
992
0
  u_int      sx, sy, xoff, yoff, size1, size2, minimum;
993
0
  u_int      new_size, saved_size, resize_first = 0;
994
0
  int      full_size = (flags & SPAWN_FULLSIZE), status;
995
0
  int      scrollbars;
996
997
  /*
998
   * If full_size is specified, add a new cell at the top of the window
999
   * layout. Otherwise, split the cell for the current pane.
1000
   */
1001
0
  if (full_size)
1002
0
    lc = wp->window->layout_root;
1003
0
  else
1004
0
    lc = wp->layout_cell;
1005
0
  status = options_get_number(wp->window->options, "pane-border-status");
1006
0
  scrollbars = options_get_number(wp->window->options, "pane-scrollbars");
1007
1008
  /* Copy the old cell size. */
1009
0
  sx = lc->sx;
1010
0
  sy = lc->sy;
1011
0
  xoff = lc->xoff;
1012
0
  yoff = lc->yoff;
1013
1014
  /* Check there is enough space for the two new panes. */
1015
0
  switch (type) {
1016
0
  case LAYOUT_LEFTRIGHT:
1017
0
    if (scrollbars) {
1018
0
      minimum = PANE_MINIMUM * 2 + sb_style->width +
1019
0
          sb_style->pad;
1020
0
    } else
1021
0
      minimum = PANE_MINIMUM * 2 + 1;
1022
0
    if (sx < minimum)
1023
0
      return (NULL);
1024
0
    break;
1025
0
  case LAYOUT_TOPBOTTOM:
1026
0
    if (layout_add_horizontal_border(wp->window, lc, status))
1027
0
      minimum = PANE_MINIMUM * 2 + 2;
1028
0
    else
1029
0
      minimum = PANE_MINIMUM * 2 + 1;
1030
0
    if (sy < minimum)
1031
0
      return (NULL);
1032
0
    break;
1033
0
  default:
1034
0
    fatalx("bad layout type");
1035
0
  }
1036
1037
  /*
1038
   * Calculate new cell sizes. size is the target size or -1 for middle
1039
   * split, size1 is the size of the top/left and size2 the bottom/right.
1040
   */
1041
0
  if (type == LAYOUT_LEFTRIGHT)
1042
0
    saved_size = sx;
1043
0
  else
1044
0
    saved_size = sy;
1045
0
  if (size < 0)
1046
0
    size2 = ((saved_size + 1) / 2) - 1;
1047
0
  else if (flags & SPAWN_BEFORE)
1048
0
    size2 = saved_size - size - 1;
1049
0
  else
1050
0
    size2 = size;
1051
0
  if (size2 < PANE_MINIMUM)
1052
0
    size2 = PANE_MINIMUM;
1053
0
  else if (size2 > saved_size - 2)
1054
0
    size2 = saved_size - 2;
1055
0
  size1 = saved_size - 1 - size2;
1056
1057
  /* Which size are we using? */
1058
0
  if (flags & SPAWN_BEFORE)
1059
0
    new_size = size2;
1060
0
  else
1061
0
    new_size = size1;
1062
1063
  /* Confirm there is enough space for full size pane. */
1064
0
  if (full_size && !layout_set_size_check(wp->window, lc, type, new_size))
1065
0
    return (NULL);
1066
1067
0
  if (lc->parent != NULL && lc->parent->type == type) {
1068
    /*
1069
     * If the parent exists and is of the same type as the split,
1070
     * create a new cell and insert it after this one.
1071
     */
1072
0
    lcparent = lc->parent;
1073
0
    lcnew = layout_create_cell(lcparent);
1074
0
    if (flags & SPAWN_BEFORE)
1075
0
      TAILQ_INSERT_BEFORE(lc, lcnew, entry);
1076
0
    else
1077
0
      TAILQ_INSERT_AFTER(&lcparent->cells, lc, lcnew, entry);
1078
0
  } else if (full_size && lc->parent == NULL && lc->type == type) {
1079
    /*
1080
     * If the new full size pane is the same type as the root
1081
     * split, insert the new pane under the existing root cell
1082
     * instead of creating a new root cell. The existing layout
1083
     * must be resized before inserting the new cell.
1084
     */
1085
0
    if (lc->type == LAYOUT_LEFTRIGHT) {
1086
0
      lc->sx = new_size;
1087
0
      layout_resize_child_cells(wp->window, lc);
1088
0
      lc->sx = saved_size;
1089
0
    } else if (lc->type == LAYOUT_TOPBOTTOM) {
1090
0
      lc->sy = new_size;
1091
0
      layout_resize_child_cells(wp->window, lc);
1092
0
      lc->sy = saved_size;
1093
0
    }
1094
0
    resize_first = 1;
1095
1096
    /* Create the new cell. */
1097
0
    lcnew = layout_create_cell(lc);
1098
0
    size = saved_size - 1 - new_size;
1099
0
    if (lc->type == LAYOUT_LEFTRIGHT)
1100
0
      layout_set_size(lcnew, size, sy, 0, 0);
1101
0
    else if (lc->type == LAYOUT_TOPBOTTOM)
1102
0
      layout_set_size(lcnew, sx, size, 0, 0);
1103
0
    if (flags & SPAWN_BEFORE)
1104
0
      TAILQ_INSERT_HEAD(&lc->cells, lcnew, entry);
1105
0
    else
1106
0
      TAILQ_INSERT_TAIL(&lc->cells, lcnew, entry);
1107
0
  } else {
1108
    /*
1109
     * Otherwise create a new parent and insert it.
1110
     */
1111
1112
    /* Create and insert the replacement parent. */
1113
0
    lcparent = layout_create_cell(lc->parent);
1114
0
    layout_make_node(lcparent, type);
1115
0
    layout_set_size(lcparent, sx, sy, xoff, yoff);
1116
0
    if (lc->parent == NULL)
1117
0
      wp->window->layout_root = lcparent;
1118
0
    else
1119
0
      TAILQ_REPLACE(&lc->parent->cells, lc, lcparent, entry);
1120
1121
    /* Insert the old cell. */
1122
0
    lc->parent = lcparent;
1123
0
    TAILQ_INSERT_HEAD(&lcparent->cells, lc, entry);
1124
1125
    /* Create the new child cell. */
1126
0
    lcnew = layout_create_cell(lcparent);
1127
0
    if (flags & SPAWN_BEFORE)
1128
0
      TAILQ_INSERT_HEAD(&lcparent->cells, lcnew, entry);
1129
0
    else
1130
0
      TAILQ_INSERT_TAIL(&lcparent->cells, lcnew, entry);
1131
0
  }
1132
0
  if (flags & SPAWN_BEFORE) {
1133
0
    lc1 = lcnew;
1134
0
    lc2 = lc;
1135
0
  } else {
1136
0
    lc1 = lc;
1137
0
    lc2 = lcnew;
1138
0
  }
1139
1140
  /*
1141
   * Set new cell sizes. size1 is the size of the top/left and size2 the
1142
   * bottom/right.
1143
   */
1144
0
  if (!resize_first && type == LAYOUT_LEFTRIGHT) {
1145
0
    layout_set_size(lc1, size1, sy, xoff, yoff);
1146
0
    layout_set_size(lc2, size2, sy, xoff + lc1->sx + 1, yoff);
1147
0
  } else if (!resize_first && type == LAYOUT_TOPBOTTOM) {
1148
0
    layout_set_size(lc1, sx, size1, xoff, yoff);
1149
0
    layout_set_size(lc2, sx, size2, xoff, yoff + lc1->sy + 1);
1150
0
  }
1151
0
  if (full_size) {
1152
0
    if (!resize_first)
1153
0
      layout_resize_child_cells(wp->window, lc);
1154
0
    layout_fix_offsets(wp->window);
1155
0
  } else
1156
0
    layout_make_leaf(lc, wp);
1157
1158
0
  return (lcnew);
1159
0
}
1160
1161
/* Destroy the cell associated with a pane. */
1162
void
1163
layout_close_pane(struct window_pane *wp)
1164
0
{
1165
0
  struct window *w = wp->window;
1166
1167
0
  if (wp->layout_cell == NULL)
1168
0
    return;
1169
1170
  /* Remove the cell. */
1171
0
  layout_destroy_cell(w, wp->layout_cell, &w->layout_root);
1172
1173
  /* Fix pane offsets and sizes. */
1174
0
  if (w->layout_root != NULL) {
1175
0
    layout_fix_offsets(w);
1176
0
    layout_fix_panes(w, NULL);
1177
0
  }
1178
0
  notify_window("window-layout-changed", w);
1179
0
}
1180
1181
int
1182
layout_spread_cell(struct window *w, struct layout_cell *parent)
1183
0
{
1184
0
  struct layout_cell  *lc;
1185
0
  u_int      number, each, size, this, remainder;
1186
0
  int      change, changed, status;
1187
1188
0
  number = 0;
1189
0
  TAILQ_FOREACH (lc, &parent->cells, entry)
1190
0
    number++;
1191
0
  if (number <= 1)
1192
0
    return (0);
1193
0
  status = options_get_number(w->options, "pane-border-status");
1194
1195
0
  if (parent->type == LAYOUT_LEFTRIGHT)
1196
0
    size = parent->sx;
1197
0
  else if (parent->type == LAYOUT_TOPBOTTOM) {
1198
0
    if (layout_add_horizontal_border(w, parent, status))
1199
0
      size = parent->sy - 1;
1200
0
    else
1201
0
      size = parent->sy;
1202
0
  } else
1203
0
    return (0);
1204
0
  if (size < number - 1)
1205
0
    return (0);
1206
0
  each = (size - (number - 1)) / number;
1207
0
  if (each == 0)
1208
0
    return (0);
1209
  /*
1210
   * Remaining space after assigning that which can be evenly
1211
   * distributed.
1212
   */
1213
0
  remainder = size - (number * (each + 1)) + 1;
1214
1215
0
  changed = 0;
1216
0
  TAILQ_FOREACH (lc, &parent->cells, entry) {
1217
0
    change = 0;
1218
0
    if (parent->type == LAYOUT_LEFTRIGHT) {
1219
0
      change = each - (int)lc->sx;
1220
0
      if (remainder > 0) {
1221
0
        change++;
1222
0
        remainder--;
1223
0
      }
1224
0
      layout_resize_adjust(w, lc, LAYOUT_LEFTRIGHT, change);
1225
0
    } else if (parent->type == LAYOUT_TOPBOTTOM) {
1226
0
      if (layout_add_horizontal_border(w, lc, status))
1227
0
        this = each + 1;
1228
0
      else
1229
0
        this = each;
1230
0
      if (remainder > 0) {
1231
0
        this++;
1232
0
        remainder--;
1233
0
      }
1234
0
      change = this - (int)lc->sy;
1235
0
      layout_resize_adjust(w, lc, LAYOUT_TOPBOTTOM, change);
1236
0
    }
1237
0
    if (change != 0)
1238
0
      changed = 1;
1239
0
  }
1240
0
  return (changed);
1241
0
}
1242
1243
void
1244
layout_spread_out(struct window_pane *wp)
1245
0
{
1246
0
  struct layout_cell  *parent;
1247
0
  struct window   *w = wp->window;
1248
1249
0
  parent = wp->layout_cell->parent;
1250
0
  if (parent == NULL)
1251
0
    return;
1252
1253
0
  do {
1254
0
    if (layout_spread_cell(w, parent)) {
1255
0
      layout_fix_offsets(w);
1256
0
      layout_fix_panes(w, NULL);
1257
0
      break;
1258
0
    }
1259
0
  } while ((parent = parent->parent) != NULL);
1260
0
}