From 6766c8ec1d1391543e1e13276054b2debd8eb81c Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Mon, 8 Jun 2026 17:18:11 -0700 Subject: [PATCH 1/4] Initial commit. --- layout.c | 3 ++- tmux.h | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/layout.c b/layout.c index d6b6ca86..a6fd7039 100644 --- a/layout.c +++ b/layout.c @@ -1328,7 +1328,8 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, * layout_assign_pane before much else happens! */ struct layout_cell * -layout_floating_pane(struct window *w, u_int sx, u_int sy, int ox, int oy) +layout_floating_pane(struct window *w, struct layout_cell *lc, u_int sx, + u_int sy, int ox, int oy) { struct layout_cell *lc = w->layout_root, *lcnew, *lcparent; diff --git a/tmux.h b/tmux.h index d3122c81..3c7f924b 100644 --- a/tmux.h +++ b/tmux.h @@ -3552,8 +3552,8 @@ void layout_assign_pane(struct layout_cell *, struct window_pane *, int); struct layout_cell *layout_split_pane(struct window_pane *, enum layout_type, int, int); -struct layout_cell *layout_floating_pane(struct window *, u_int, u_int, int, - int); +struct layout_cell *layout_floating_pane(struct window *, struct layout_cell *, + u_int, u_int, int, int); void layout_close_pane(struct window_pane *); int layout_spread_cell(struct window *, struct layout_cell *); void layout_spread_out(struct window_pane *); From 7568bff8e61079dbaf29f8e4e6cf2dbdb0f831ab Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Mon, 8 Jun 2026 18:59:07 -0700 Subject: [PATCH 2/4] Layout cells for floating panes are now inserted after the cell of a provided window pane. --- layout.c | 21 ++++++++++++--------- tmux.h | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/layout.c b/layout.c index a6fd7039..f453eaf8 100644 --- a/layout.c +++ b/layout.c @@ -1328,12 +1328,16 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, * layout_assign_pane before much else happens! */ struct layout_cell * -layout_floating_pane(struct window *w, struct layout_cell *lc, u_int sx, +layout_floating_pane(struct window *w, struct window_pane *wp, u_int sx, u_int sy, int ox, int oy) { - struct layout_cell *lc = w->layout_root, *lcnew, *lcparent; + struct layout_cell *lc = wp->layout_cell, *lcnew, *lcparent; - if (lc->type == LAYOUT_WINDOWPANE) { + if (lc == NULL) + lc = w->layout_root; + lcparent = lc->parent; + + if (lcparent == NULL) { /* * Adding a pane to a root that doesn't have a container. Must * create and insert a new root. @@ -1346,11 +1350,10 @@ layout_floating_pane(struct window *w, struct layout_cell *lc, u_int sx, /* Insert the old cell. */ lc->parent = lcparent; TAILQ_INSERT_HEAD(&lcparent->cells, lc, entry); - } else - lcparent = w->layout_root; + } lcnew = layout_create_cell(lcparent); - TAILQ_INSERT_TAIL(&lcparent->cells, lcnew, entry); + TAILQ_INSERT_AFTER(&lcparent->cells, lc, lcnew, entry); lcnew->flags |= LAYOUT_CELL_FLOATING; layout_set_size(lcnew, sx, sy, ox, oy); @@ -1525,10 +1528,10 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args, /* Get a new floating cell. */ struct layout_cell * layout_get_floating_cell(struct cmdq_item *item, struct args *args, - struct window *w, __unused struct window_pane *wp, char **cause) + struct window *w, struct window_pane *wp, char **cause) { struct layout_cell *lcnew; - int sx = w->sx / 2, sy = w->sy / 4; + u_int sx = w->sx / 2, sy = w->sy / 4; int ox = INT_MAX, oy = INT_MAX; if (args_has(args, 'x')) { @@ -1577,6 +1580,6 @@ layout_get_floating_cell(struct cmdq_item *item, struct args *args, w->last_new_pane_y = oy; } - lcnew = layout_floating_pane(w, sx, sy, ox, oy); + lcnew = layout_floating_pane(w, wp, sx, sy, ox, oy); return (lcnew); } diff --git a/tmux.h b/tmux.h index 3c7f924b..4e4e8e72 100644 --- a/tmux.h +++ b/tmux.h @@ -3552,7 +3552,7 @@ void layout_assign_pane(struct layout_cell *, struct window_pane *, int); struct layout_cell *layout_split_pane(struct window_pane *, enum layout_type, int, int); -struct layout_cell *layout_floating_pane(struct window *, struct layout_cell *, +struct layout_cell *layout_floating_pane(struct window *, struct window_pane *, u_int, u_int, int, int); void layout_close_pane(struct window_pane *); int layout_spread_cell(struct window *, struct layout_cell *); From 2a1ad05671200efdfb63ca429f7affce74cba606 Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Tue, 9 Jun 2026 13:28:38 -0700 Subject: [PATCH 3/4] There was an bug with the earlier commit, reproduction: "splitw; newp -t0; killp -t0". Added logic to handle previously unforseen states, like a floating cell before a tiled cell at the top of the screen. --- layout.c | 238 ++++++++++++++++++++++++++++++------------------------- tmux.h | 5 +- window.c | 28 +------ 3 files changed, 135 insertions(+), 136 deletions(-) diff --git a/layout.c b/layout.c index f453eaf8..935c5232 100644 --- a/layout.c +++ b/layout.c @@ -250,10 +250,7 @@ layout_fix_offsets1(struct layout_cell *lc) if (lc->type == LAYOUT_LEFTRIGHT) { xoff = lc->xoff; TAILQ_FOREACH(lcchild, &lc->cells, entry) { - if (lcchild->flags & LAYOUT_CELL_FLOATING || - (lcchild->type == LAYOUT_WINDOWPANE && - lcchild->wp != NULL && - (lcchild->wp->flags & PANE_HIDDEN))) + if (!layout_cell_is_tiled(lcchild)) continue; lcchild->xoff = xoff; lcchild->yoff = lc->yoff; @@ -264,10 +261,7 @@ layout_fix_offsets1(struct layout_cell *lc) } else { yoff = lc->yoff; TAILQ_FOREACH(lcchild, &lc->cells, entry) { - if (lcchild->flags & LAYOUT_CELL_FLOATING || - (lcchild->type == LAYOUT_WINDOWPANE && - lcchild->wp != NULL && - (lcchild->wp->flags & PANE_HIDDEN))) + if (!layout_cell_is_tiled(lcchild)) continue; lcchild->xoff = lc->xoff; lcchild->yoff = yoff; @@ -294,6 +288,29 @@ layout_fix_offsets(struct window *w) layout_fix_offsets1(lc); } +int +layout_cell_is_tiled(struct layout_cell *lc) +{ + return ((~lc->flags & LAYOUT_CELL_HIDDEN) && + (~lc->flags & LAYOUT_CELL_FLOATING)); +} + +static int +layout_cell_is_first_tiled(struct layout_cell *lc) +{ + struct layout_cell *lcchild, *lcparent = lc->parent; + + if (lcparent == NULL) + return (layout_cell_is_tiled(lc)); + + TAILQ_FOREACH(lcchild, &lcparent->cells, entry) { + if (layout_cell_is_tiled(lcchild)) + break; + } + + return (lcchild == lc); +} + /* Is this a top cell? */ static int layout_cell_is_top(struct window *w, struct layout_cell *lc) @@ -305,7 +322,7 @@ layout_cell_is_top(struct window *w, struct layout_cell *lc) if (next == NULL) return (0); if (next->type == LAYOUT_TOPBOTTOM && - lc != TAILQ_FIRST(&next->cells)) + !layout_cell_is_first_tiled(lc)) return (0); lc = next; } @@ -497,7 +514,7 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc, /* Child cell runs in a different direction. */ if (lc->type != type) { TAILQ_FOREACH(lcchild, &lc->cells, entry) { - if (lcchild->flags & LAYOUT_CELL_FLOATING) + if (!layout_cell_is_tiled(lcchild)) continue; layout_resize_adjust(w, lcchild, type, change); } @@ -512,7 +529,7 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc, TAILQ_FOREACH(lcchild, &lc->cells, entry) { if (change == 0) break; - if (lcchild->flags & LAYOUT_CELL_FLOATING) + if (!layout_cell_is_tiled(lcchild)) continue; if (change > 0) { layout_resize_adjust(w, lcchild, type, 1); @@ -527,36 +544,6 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc, } } -/* - * Return the nearest sibling of lc that is not a hidden WINDOWPANE leaf, - * walking forward (direction=1) or backward (direction=0) in the parent's list. - * Container cells (TOPBOTTOM/LEFTRIGHT) are never skipped. - */ -static struct layout_cell * -layout_active_neighbour(struct layout_cell *lc, int direction) -{ - struct layout_cell *lcother; - - if (direction) - lcother = TAILQ_NEXT(lc, entry); - else - lcother = TAILQ_PREV(lc, layout_cells, entry); - - while (lcother != NULL) { - if (lcother->type != LAYOUT_WINDOWPANE) - return (lcother); /* container — not skipped */ - if (lcother->wp == NULL || - (~lcother->wp->flags & PANE_HIDDEN)) - return (lcother); /* visible leaf */ - /* hidden leaf — keep walking */ - if (direction) - lcother = TAILQ_NEXT(lcother, entry); - else - lcother = TAILQ_PREV(lcother, layout_cells, entry); - } - return (NULL); -} - /* * Redistribute space equally among all visible (non-hidden WINDOWPANE) * children of lcparent in the given direction. Hidden WINDOWPANE leaves @@ -613,14 +600,62 @@ layout_redistribute_cells(struct window *w, struct layout_cell *lcparent, } } -/* Destroy a cell and redistribute the space in tiled cells. */ +/* Helper function for layout_cell_get_neighbour. */ +static struct layout_cell * +layout_cell_get_neighbour_direction(struct layout_cell *lc, int direction) +{ + struct layout_cell *lcother = lc; + + while (1) { + if (direction) + lcother = TAILQ_NEXT(lcother, entry); + else + lcother = TAILQ_PREV(lcother, layout_cells, entry); + + if (lcother == NULL || layout_cell_is_tiled(lcother)) + return (lcother); + } +} + +/* + * Finds the nearest visible neighbour. A neighbour is a sibling cell drawn + * within the tiled layout. Prefers cells "before" the specified cell. + * This behavior defines how cell dimensions are redistributed when a cell is + * hidden/shown and floated/tiled. + */ +struct layout_cell * +layout_cell_get_neighbour(struct layout_cell *lc) +{ + struct layout_cell *lcother, *lcparent = lc->parent; + int direction = 0; + + if (lcparent == NULL) + return (NULL); + + if (lc == TAILQ_FIRST(&lcparent->cells)) + direction = 1; + + lcother = layout_cell_get_neighbour_direction(lc, direction); + if (lcother == NULL) + lcother = layout_cell_get_neighbour_direction(lc, !direction); + + return lcother; +} + + +/* Destroy a cell and redistribute the space if the cell was tiled. */ void layout_destroy_cell(struct window *w, struct layout_cell *lc, struct layout_cell **lcroot) { - struct layout_cell *lcother = NULL, *lcparent; + struct layout_cell *lcother, *lcparent; + int val; - /* If no parent, this is the last pane in a window. */ + /* + * If no parent, this is either a floating pane or the last + * pane so window close is imminent and there is no need to + * resize anything. + */ lcparent = lc->parent; if (lcparent == NULL) { if (lc->wp != NULL) @@ -629,30 +664,30 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, return; } - if (~lc->flags & LAYOUT_CELL_FLOATING) { - /* Merge the space into the previous or next cell. */ - if (lc == TAILQ_FIRST(&lcparent->cells)) - lcother = TAILQ_NEXT(lc, entry); + if (!layout_cell_is_tiled(lc)) { + TAILQ_REMOVE(&lcparent->cells, lc, entry); + layout_free_cell(lc); + goto out; + } + + lcother = layout_cell_get_neighbour(lc); + if (lcother != NULL) { + if (lcparent->type == LAYOUT_LEFTRIGHT) + val = lc->sx + 1; else - lcother = TAILQ_PREV(lc, layout_cells, entry); - } - if (lcother != NULL && (~lcother->flags & LAYOUT_CELL_FLOATING)) { - if (lcparent->type == LAYOUT_LEFTRIGHT) { - layout_resize_adjust(w, lcother, lcparent->type, - lc->sx + 1); - } else { - layout_resize_adjust(w, lcother, lcparent->type, - lc->sy + 1); - } - } + val = lc->sy + 1; + layout_resize_adjust(w, lcother, lcparent->type, val); + } else + layout_hide_cell(w, lcparent); /* Remove this from the parent's list. */ TAILQ_REMOVE(&lcparent->cells, lc, entry); layout_free_cell(lc); +out: /* - * In tiled layouts, if the parent now has one cell, remove - * the parent from the tree and replace it by that cell. + * If the parent now has one cell, remove the parent from the tree and + * replace it by that cell. */ lc = TAILQ_FIRST(&lcparent->cells); if (lc != NULL && TAILQ_NEXT(lc, entry) == NULL) { @@ -660,24 +695,8 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, lc->parent = lcparent->parent; if (lc->parent == NULL) { - if (~lc->flags & LAYOUT_CELL_FLOATING) { - lc->xoff = 0; - lc->yoff = 0; - } - - /* - * If the sole remaining child is a hidden - * WINDOWPANE, its stored size may be stale (it never - * received the space that was given to the removed - * cell). Restore the full window size so that - * 'show' can reclaim the correct amount. - */ - if (lc->type == LAYOUT_WINDOWPANE && - lc->wp != NULL && - (lc->wp->flags & PANE_HIDDEN)) { - lc->sx = lcparent->sx; - lc->sy = lcparent->sy; - } + if (layout_cell_is_tiled(lc)) + layout_set_size(lc, w->sx, w->sy, 0, 0); *lcroot = lc; } else TAILQ_REPLACE(&lc->parent->cells, lcparent, lc, entry); @@ -686,43 +705,46 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, } } -/* Hide a cell and redistribute the space in tiled cells. */ + +/* Hide a cell. Space is redistributed to the nearest neighbour if the cell was + * tiled. + */ void layout_hide_cell(struct window *w, struct layout_cell *lc) { - struct layout_cell *lcother, *lcparent, *lcchild; - u_int space = 0; - int direction; + struct layout_cell *lcother, *lcparent, *lcchild; + u_int shown_children = 0; + int val; - lcparent = lc->parent; - if (lcparent == NULL) + if (lc == NULL) return; - /* Merge the space into the nearest non-hidden sibling. */ - { - direction = (lc == TAILQ_FIRST(&lcparent->cells)) ? 1 : 0; - lcother = layout_active_neighbour(lc, direction); - if (lcother == NULL) - lcother = layout_active_neighbour(lc, !direction); - } - if (lcother != NULL && lcparent->type == LAYOUT_LEFTRIGHT) - layout_resize_adjust(w, lcother, lcparent->type, lc->sx + 1); - else if (lcother != NULL) - layout_resize_adjust(w, lcother, lcparent->type, lc->sy + 1); + lcparent = lc->parent; + lc->flags |= LAYOUT_CELL_HIDDEN; - /* If the parent cells are all hidden, hide it too. */ + /* Merge the space into the nearest neighbour. */ + lcother = layout_cell_get_neighbour(lc); + + if (lcother != NULL) { + if (lcparent->type == LAYOUT_LEFTRIGHT) + val = lc->sx + 1; + else + val = lc->sy + 1; + layout_resize_adjust(w, lcother, lcparent->type, val); + } + + if (layout_cell_is_tiled(lc)) + layout_set_size(lc, 0, 0, 0, 0); + + /* If no children are tiled, hide the parent. */ if (lcparent != NULL) { TAILQ_FOREACH(lcchild, &lcparent->cells, entry) { - if (lcchild->wp == NULL || - lcchild->wp->flags & PANE_HIDDEN) - continue; - if (lcparent->type == LAYOUT_LEFTRIGHT) { - space += lcchild->sx; - } else if (lcparent->type == LAYOUT_TOPBOTTOM) { - space += lcchild->sy; + if (layout_cell_is_tiled(lcchild)) { + shown_children = 1; + break; } } - if (space == 0) + if (shown_children == 0) layout_hide_cell(w, lcparent); } } @@ -787,7 +809,7 @@ layout_resize(struct window *w, u_int sx, u_int sy) * out proportionately - this should leave the layout fitting the new * window size. */ - if (lc->type == LAYOUT_WINDOWPANE && (lc->flags & LAYOUT_CELL_FLOATING)) + if (lc->type == LAYOUT_WINDOWPANE && !layout_cell_is_tiled(lc)) return; xchange = sx - lc->sx; xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT); @@ -1101,7 +1123,7 @@ layout_resize_child_cells(struct window *w, struct layout_cell *lc) count = 0; previous = 0; TAILQ_FOREACH(lcchild, &lc->cells, entry) { - if (lcchild->flags & LAYOUT_CELL_FLOATING) + if (!layout_cell_is_tiled(lcchild)) continue; count++; if (lc->type == LAYOUT_LEFTRIGHT) @@ -1121,7 +1143,7 @@ layout_resize_child_cells(struct window *w, struct layout_cell *lc) /* Resize children into the new size. */ idx = 0; TAILQ_FOREACH(lcchild, &lc->cells, entry) { - if (lcchild->flags & LAYOUT_CELL_FLOATING) + if (!layout_cell_is_tiled(lcchild)) continue; if (lc->type == LAYOUT_TOPBOTTOM) { lcchild->sx = lc->sx; diff --git a/tmux.h b/tmux.h index 4e4e8e72..47542d0e 100644 --- a/tmux.h +++ b/tmux.h @@ -1486,7 +1486,8 @@ TAILQ_HEAD(layout_cells, layout_cell); struct layout_cell { enum layout_type type; -#define LAYOUT_CELL_FLOATING 0x1 +#define LAYOUT_CELL_FLOATING 0x1 +#define LAYOUT_CELL_HIDDEN 0x2 int flags; struct layout_cell *parent; @@ -3538,9 +3539,11 @@ void layout_make_leaf(struct layout_cell *, struct window_pane *); void layout_make_node(struct layout_cell *, enum layout_type); void layout_fix_zindexes(struct window *, struct layout_cell *); void layout_fix_offsets(struct window *); +int layout_cell_is_tiled(struct layout_cell *); void layout_fix_panes(struct window *, struct window_pane *); void layout_resize_adjust(struct window *, struct layout_cell *, enum layout_type, int); +struct layout_cell *layout_cell_get_neighbour(struct layout_cell *); void layout_init(struct window *, struct window_pane *); void layout_free(struct window *); void layout_resize(struct window *, u_int, u_int); diff --git a/window.c b/window.c index 458b2343..9b2a8bd9 100644 --- a/window.c +++ b/window.c @@ -751,21 +751,6 @@ window_zoom(struct window_pane *wp) window_set_active_pane(w, wp, 1); wp->flags |= PANE_ZOOMED; - /* Bring pane above other tiled panes and hide floating panes. */ - TAILQ_FOREACH(wp1, &w->z_index, zentry) { - if (wp1 == wp) { - wp1->saved_flags |= (wp1->flags & PANE_HIDDEN); - wp1->flags &= ~PANE_HIDDEN; - continue; - } - if (window_pane_is_floating(wp1)) { - wp1->saved_flags |= (wp1->flags & PANE_HIDDEN); - wp1->flags |= PANE_HIDDEN; - continue; - } - break; - } - TAILQ_FOREACH(wp1, &w->panes, entry) { wp1->saved_layout_cell = wp1->layout_cell; wp1->layout_cell = NULL; @@ -792,20 +777,9 @@ window_unzoom(struct window *w, int notify) w->layout_root = w->saved_layout_root; w->saved_layout_root = NULL; - TAILQ_FOREACH(wp, &w->z_index, zentry) { - if (window_pane_is_floating(wp)) { - wp->flags &= ~PANE_HIDDEN | (wp->saved_flags & PANE_HIDDEN); - continue; - } - break; - } - TAILQ_FOREACH(wp, &w->panes, entry) { wp->layout_cell = wp->saved_layout_cell; - if (wp->flags & PANE_HIDDEN) - wp->saved_layout_cell = wp->layout_cell; - else - wp->saved_layout_cell = NULL; + wp->saved_layout_cell = NULL; wp->flags &= ~PANE_ZOOMED; } layout_fix_panes(w, NULL); From e370ce5a003ba4ccf3328321b00c52f131b4ff93 Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Tue, 9 Jun 2026 15:44:24 -0700 Subject: [PATCH 4/4] Added function comment. --- layout.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/layout.c b/layout.c index 935c5232..853b0b6f 100644 --- a/layout.c +++ b/layout.c @@ -288,6 +288,10 @@ layout_fix_offsets(struct window *w) layout_fix_offsets1(lc); } +/* + * Not all cells are drawn within the tiled grid of a layout. This predicate + * isolates that logic. + */ int layout_cell_is_tiled(struct layout_cell *lc) {