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.
This commit is contained in:
Dane Jensen
2026-06-09 13:28:38 -07:00
parent 7568bff8e6
commit 2a1ad05671
3 changed files with 135 additions and 136 deletions

238
layout.c
View File

@@ -250,10 +250,7 @@ layout_fix_offsets1(struct layout_cell *lc)
if (lc->type == LAYOUT_LEFTRIGHT) { if (lc->type == LAYOUT_LEFTRIGHT) {
xoff = lc->xoff; xoff = lc->xoff;
TAILQ_FOREACH(lcchild, &lc->cells, entry) { TAILQ_FOREACH(lcchild, &lc->cells, entry) {
if (lcchild->flags & LAYOUT_CELL_FLOATING || if (!layout_cell_is_tiled(lcchild))
(lcchild->type == LAYOUT_WINDOWPANE &&
lcchild->wp != NULL &&
(lcchild->wp->flags & PANE_HIDDEN)))
continue; continue;
lcchild->xoff = xoff; lcchild->xoff = xoff;
lcchild->yoff = lc->yoff; lcchild->yoff = lc->yoff;
@@ -264,10 +261,7 @@ layout_fix_offsets1(struct layout_cell *lc)
} else { } else {
yoff = lc->yoff; yoff = lc->yoff;
TAILQ_FOREACH(lcchild, &lc->cells, entry) { TAILQ_FOREACH(lcchild, &lc->cells, entry) {
if (lcchild->flags & LAYOUT_CELL_FLOATING || if (!layout_cell_is_tiled(lcchild))
(lcchild->type == LAYOUT_WINDOWPANE &&
lcchild->wp != NULL &&
(lcchild->wp->flags & PANE_HIDDEN)))
continue; continue;
lcchild->xoff = lc->xoff; lcchild->xoff = lc->xoff;
lcchild->yoff = yoff; lcchild->yoff = yoff;
@@ -294,6 +288,29 @@ layout_fix_offsets(struct window *w)
layout_fix_offsets1(lc); 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? */ /* Is this a top cell? */
static int static int
layout_cell_is_top(struct window *w, struct layout_cell *lc) 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) if (next == NULL)
return (0); return (0);
if (next->type == LAYOUT_TOPBOTTOM && if (next->type == LAYOUT_TOPBOTTOM &&
lc != TAILQ_FIRST(&next->cells)) !layout_cell_is_first_tiled(lc))
return (0); return (0);
lc = next; lc = next;
} }
@@ -497,7 +514,7 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc,
/* Child cell runs in a different direction. */ /* Child cell runs in a different direction. */
if (lc->type != type) { if (lc->type != type) {
TAILQ_FOREACH(lcchild, &lc->cells, entry) { TAILQ_FOREACH(lcchild, &lc->cells, entry) {
if (lcchild->flags & LAYOUT_CELL_FLOATING) if (!layout_cell_is_tiled(lcchild))
continue; continue;
layout_resize_adjust(w, lcchild, type, change); 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) { TAILQ_FOREACH(lcchild, &lc->cells, entry) {
if (change == 0) if (change == 0)
break; break;
if (lcchild->flags & LAYOUT_CELL_FLOATING) if (!layout_cell_is_tiled(lcchild))
continue; continue;
if (change > 0) { if (change > 0) {
layout_resize_adjust(w, lcchild, type, 1); 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) * Redistribute space equally among all visible (non-hidden WINDOWPANE)
* children of lcparent in the given direction. Hidden WINDOWPANE leaves * 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 void
layout_destroy_cell(struct window *w, struct layout_cell *lc, layout_destroy_cell(struct window *w, struct layout_cell *lc,
struct layout_cell **lcroot) 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; lcparent = lc->parent;
if (lcparent == NULL) { if (lcparent == NULL) {
if (lc->wp != NULL) if (lc->wp != NULL)
@@ -629,30 +664,30 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc,
return; return;
} }
if (~lc->flags & LAYOUT_CELL_FLOATING) { if (!layout_cell_is_tiled(lc)) {
/* Merge the space into the previous or next cell. */ TAILQ_REMOVE(&lcparent->cells, lc, entry);
if (lc == TAILQ_FIRST(&lcparent->cells)) layout_free_cell(lc);
lcother = TAILQ_NEXT(lc, entry); goto out;
}
lcother = layout_cell_get_neighbour(lc);
if (lcother != NULL) {
if (lcparent->type == LAYOUT_LEFTRIGHT)
val = lc->sx + 1;
else else
lcother = TAILQ_PREV(lc, layout_cells, entry); val = lc->sy + 1;
} layout_resize_adjust(w, lcother, lcparent->type, val);
if (lcother != NULL && (~lcother->flags & LAYOUT_CELL_FLOATING)) { } else
if (lcparent->type == LAYOUT_LEFTRIGHT) { layout_hide_cell(w, lcparent);
layout_resize_adjust(w, lcother, lcparent->type,
lc->sx + 1);
} else {
layout_resize_adjust(w, lcother, lcparent->type,
lc->sy + 1);
}
}
/* Remove this from the parent's list. */ /* Remove this from the parent's list. */
TAILQ_REMOVE(&lcparent->cells, lc, entry); TAILQ_REMOVE(&lcparent->cells, lc, entry);
layout_free_cell(lc); layout_free_cell(lc);
out:
/* /*
* In tiled layouts, if the parent now has one cell, remove * If the parent now has one cell, remove the parent from the tree and
* the parent from the tree and replace it by that cell. * replace it by that cell.
*/ */
lc = TAILQ_FIRST(&lcparent->cells); lc = TAILQ_FIRST(&lcparent->cells);
if (lc != NULL && TAILQ_NEXT(lc, entry) == NULL) { 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; lc->parent = lcparent->parent;
if (lc->parent == NULL) { if (lc->parent == NULL) {
if (~lc->flags & LAYOUT_CELL_FLOATING) { if (layout_cell_is_tiled(lc))
lc->xoff = 0; layout_set_size(lc, w->sx, w->sy, 0, 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;
}
*lcroot = lc; *lcroot = lc;
} else } else
TAILQ_REPLACE(&lc->parent->cells, lcparent, lc, entry); 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 void
layout_hide_cell(struct window *w, struct layout_cell *lc) layout_hide_cell(struct window *w, struct layout_cell *lc)
{ {
struct layout_cell *lcother, *lcparent, *lcchild; struct layout_cell *lcother, *lcparent, *lcchild;
u_int space = 0; u_int shown_children = 0;
int direction; int val;
lcparent = lc->parent; if (lc == NULL)
if (lcparent == NULL)
return; return;
/* Merge the space into the nearest non-hidden sibling. */ lcparent = lc->parent;
{ lc->flags |= LAYOUT_CELL_HIDDEN;
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);
/* 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) { if (lcparent != NULL) {
TAILQ_FOREACH(lcchild, &lcparent->cells, entry) { TAILQ_FOREACH(lcchild, &lcparent->cells, entry) {
if (lcchild->wp == NULL || if (layout_cell_is_tiled(lcchild)) {
lcchild->wp->flags & PANE_HIDDEN) shown_children = 1;
continue; break;
if (lcparent->type == LAYOUT_LEFTRIGHT) {
space += lcchild->sx;
} else if (lcparent->type == LAYOUT_TOPBOTTOM) {
space += lcchild->sy;
} }
} }
if (space == 0) if (shown_children == 0)
layout_hide_cell(w, lcparent); 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 * out proportionately - this should leave the layout fitting the new
* window size. * window size.
*/ */
if (lc->type == LAYOUT_WINDOWPANE && (lc->flags & LAYOUT_CELL_FLOATING)) if (lc->type == LAYOUT_WINDOWPANE && !layout_cell_is_tiled(lc))
return; return;
xchange = sx - lc->sx; xchange = sx - lc->sx;
xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT); 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; count = 0;
previous = 0; previous = 0;
TAILQ_FOREACH(lcchild, &lc->cells, entry) { TAILQ_FOREACH(lcchild, &lc->cells, entry) {
if (lcchild->flags & LAYOUT_CELL_FLOATING) if (!layout_cell_is_tiled(lcchild))
continue; continue;
count++; count++;
if (lc->type == LAYOUT_LEFTRIGHT) 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. */ /* Resize children into the new size. */
idx = 0; idx = 0;
TAILQ_FOREACH(lcchild, &lc->cells, entry) { TAILQ_FOREACH(lcchild, &lc->cells, entry) {
if (lcchild->flags & LAYOUT_CELL_FLOATING) if (!layout_cell_is_tiled(lcchild))
continue; continue;
if (lc->type == LAYOUT_TOPBOTTOM) { if (lc->type == LAYOUT_TOPBOTTOM) {
lcchild->sx = lc->sx; lcchild->sx = lc->sx;

5
tmux.h
View File

@@ -1486,7 +1486,8 @@ TAILQ_HEAD(layout_cells, layout_cell);
struct layout_cell { struct layout_cell {
enum layout_type type; enum layout_type type;
#define LAYOUT_CELL_FLOATING 0x1 #define LAYOUT_CELL_FLOATING 0x1
#define LAYOUT_CELL_HIDDEN 0x2
int flags; int flags;
struct layout_cell *parent; 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_make_node(struct layout_cell *, enum layout_type);
void layout_fix_zindexes(struct window *, struct layout_cell *); void layout_fix_zindexes(struct window *, struct layout_cell *);
void layout_fix_offsets(struct window *); 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_fix_panes(struct window *, struct window_pane *);
void layout_resize_adjust(struct window *, struct layout_cell *, void layout_resize_adjust(struct window *, struct layout_cell *,
enum layout_type, int); enum layout_type, int);
struct layout_cell *layout_cell_get_neighbour(struct layout_cell *);
void layout_init(struct window *, struct window_pane *); void layout_init(struct window *, struct window_pane *);
void layout_free(struct window *); void layout_free(struct window *);
void layout_resize(struct window *, u_int, u_int); void layout_resize(struct window *, u_int, u_int);

View File

@@ -751,21 +751,6 @@ window_zoom(struct window_pane *wp)
window_set_active_pane(w, wp, 1); window_set_active_pane(w, wp, 1);
wp->flags |= PANE_ZOOMED; 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) { TAILQ_FOREACH(wp1, &w->panes, entry) {
wp1->saved_layout_cell = wp1->layout_cell; wp1->saved_layout_cell = wp1->layout_cell;
wp1->layout_cell = NULL; wp1->layout_cell = NULL;
@@ -792,20 +777,9 @@ window_unzoom(struct window *w, int notify)
w->layout_root = w->saved_layout_root; w->layout_root = w->saved_layout_root;
w->saved_layout_root = NULL; 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) { TAILQ_FOREACH(wp, &w->panes, entry) {
wp->layout_cell = wp->saved_layout_cell; wp->layout_cell = wp->saved_layout_cell;
if (wp->flags & PANE_HIDDEN) wp->saved_layout_cell = NULL;
wp->saved_layout_cell = wp->layout_cell;
else
wp->saved_layout_cell = NULL;
wp->flags &= ~PANE_ZOOMED; wp->flags &= ~PANE_ZOOMED;
} }
layout_fix_panes(w, NULL); layout_fix_panes(w, NULL);