Fix bugs with tiling floating panes.

This commit is contained in:
Michael Grant
2026-04-05 19:04:10 -04:00
parent 4232bf9e2f
commit 811604a663
5 changed files with 230 additions and 75 deletions

View File

@@ -143,6 +143,10 @@ cmd_minimise_pane_minimise(struct window *w, struct window_pane *wp)
{
struct window_pane *wp2;
/* Ignore if already minimised to prevent double-redistribution. */
if (wp->flags & PANE_MINIMISED)
return (CMD_RETURN_NORMAL);
wp->flags |= PANE_MINIMISED;
window_deactivate_pane(w, wp, 1);

View File

@@ -36,11 +36,6 @@
static enum cmd_retval cmd_float_pane_exec(struct cmd *, struct cmdq_item *);
static enum cmd_retval cmd_tile_pane_exec(struct cmd *, struct cmdq_item *);
static enum cmd_retval do_float_pane(struct window *, struct window_pane *,
int, int, u_int, u_int);
static enum cmd_retval do_tile_pane(struct window *, struct window_pane *,
struct cmdq_item *);
const struct cmd_entry cmd_float_pane_entry = {
.name = "float-pane",
.alias = NULL,
@@ -76,7 +71,7 @@ const struct cmd_entry cmd_tile_pane_entry = {
* caller's statics.
*/
static int
parse_float_geometry(struct args *args, struct cmdq_item *item,
cmd_float_pane_parse_geometry(struct args *args, struct cmdq_item *item,
struct window *w, int *out_x, int *out_y, u_int *out_sx, u_int *out_sy,
int *last_x, int *last_y)
{
@@ -162,6 +157,7 @@ cmd_float_pane_exec(struct cmd *self, struct cmdq_item *item)
static int last_x = 0, last_y = 0;
int x, y;
u_int sx, sy;
struct layout_cell *lc;
if (wp->flags & PANE_FLOATING) {
cmdq_error(item, "pane is already floating");
@@ -188,40 +184,11 @@ cmd_float_pane_exec(struct cmd *self, struct cmdq_item *item)
sx = wp->saved_float_sx;
sy = wp->saved_float_sy;
} else {
if (parse_float_geometry(args, item, w, &x, &y, &sx, &sy,
&last_x, &last_y) != 0)
if (cmd_float_pane_parse_geometry(args, item, w, &x, &y, &sx,
&sy, &last_x, &last_y) != 0)
return (CMD_RETURN_ERROR);
}
return (do_float_pane(w, wp, x, y, sx, sy));
}
static enum cmd_retval
cmd_tile_pane_exec(struct cmd *self, struct cmdq_item *item)
{
__attribute((unused)) struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item);
struct window *w = target->wl->window;
struct window_pane *wp = target->wp;
if (!(wp->flags & PANE_FLOATING)) {
cmdq_error(item, "pane is not floating");
return (CMD_RETURN_ERROR);
}
if (w->flags & WINDOW_ZOOMED) {
cmdq_error(item, "can't tile a pane while window is zoomed");
return (CMD_RETURN_ERROR);
}
return (do_tile_pane(w, wp, item));
}
static enum cmd_retval
do_float_pane(struct window *w, struct window_pane *wp, int x, int y,
u_int sx, u_int sy)
{
struct layout_cell *lc;
/*
* Remove the pane from the tiled layout tree so neighbours reclaim
* the space. layout_close_pane calls layout_destroy_cell which frees
@@ -254,10 +221,26 @@ do_float_pane(struct window *w, struct window_pane *wp, int x, int y,
}
static enum cmd_retval
do_tile_pane(struct window *w, struct window_pane *wp, struct cmdq_item *item)
cmd_tile_pane_exec(struct cmd *self, struct cmdq_item *item)
{
struct window_pane *target_wp;
__attribute((unused)) struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item);
struct window *w = target->wl->window;
struct window_pane *wp = target->wp;
struct window_pane *target_wp, *wpiter;
struct layout_cell *float_lc, *lc;
int was_minimised;
if (!(wp->flags & PANE_FLOATING)) {
cmdq_error(item, "pane is not floating");
return (CMD_RETURN_ERROR);
}
if (w->flags & WINDOW_ZOOMED) {
cmdq_error(item, "can't tile a pane while window is zoomed");
return (CMD_RETURN_ERROR);
}
was_minimised = (wp->flags & PANE_MINIMISED) != 0;
/*
* Save the floating geometry so we can restore it next time this pane
@@ -270,37 +253,58 @@ do_tile_pane(struct window *w, struct window_pane *wp, struct cmdq_item *item)
wp->saved_float_sy = float_lc->sy;
wp->flags |= PANE_SAVED_FLOAT;
/*
* If the pane is also minimised, clear saved_layout_cell before
* freeing the floating cell — otherwise the pointer would dangle.
*/
if (was_minimised)
wp->saved_layout_cell = NULL;
/*
* Free the detached floating cell. Clear its wp pointer first so
* layout_free_cell's WINDOWPANE case does not corrupt wp->layout_cell.
*/
float_lc->wp = NULL;
layout_free_cell(float_lc); /* wp->layout_cell already NULL */
layout_free_cell(float_lc);
wp->layout_cell = NULL;
/*
* Find the best tiled pane to split after: prefer the active pane
* (if tiled), then the most-recently-visited tiled pane, then any
* visible tiled pane.
* Find the best tiled pane to split after, prefer a visible (non-
* minimised) tiled pane. If all tiled panes are minimised, fall back
* to any tiled pane so the new pane enters the existing tree rather
* than becoming a disconnected root.
*/
target_wp = NULL;
if (w->active != NULL && !(w->active->flags & PANE_FLOATING))
if (w->active != NULL && !(w->active->flags & PANE_FLOATING) &&
!(w->active->flags & PANE_MINIMISED))
target_wp = w->active;
if (target_wp == NULL) {
TAILQ_FOREACH(target_wp, &w->last_panes, sentry) {
if (!(target_wp->flags & PANE_FLOATING) &&
window_pane_visible(target_wp))
TAILQ_FOREACH(wpiter, &w->last_panes, sentry) {
if (!(wpiter->flags & (PANE_FLOATING|PANE_MINIMISED)) &&
window_pane_visible(wpiter)) {
target_wp = wpiter;
break;
}
}
}
if (target_wp == NULL) {
TAILQ_FOREACH(target_wp, &w->panes, entry) {
if (!(target_wp->flags & PANE_FLOATING) &&
window_pane_visible(target_wp))
TAILQ_FOREACH(wpiter, &w->panes, entry) {
if (!(wpiter->flags & (PANE_FLOATING|PANE_MINIMISED)) &&
window_pane_visible(wpiter)) {
target_wp = wpiter;
break;
}
}
}
/* Fall back to any tiled pane (even minimised) to stay in the tree. */
if (target_wp == NULL) {
TAILQ_FOREACH(wpiter, &w->panes, entry) {
if (!(wpiter->flags & PANE_FLOATING)) {
target_wp = wpiter;
break;
}
}
}
if (target_wp != NULL) {
lc = layout_split_pane(target_wp, LAYOUT_TOPBOTTOM, -1, 0);
if (lc == NULL)
@@ -311,6 +315,14 @@ do_tile_pane(struct window *w, struct window_pane *wp, struct cmdq_item *item)
return (CMD_RETURN_ERROR);
}
layout_assign_pane(lc, wp, 0);
/*
* Redistribute space equally among all visible panes at this
* level, so the new pane gets an equal share rather than just
* half of the split target.
*/
if (wp->layout_cell != NULL && wp->layout_cell->parent != NULL)
layout_redistribute_cells(w, wp->layout_cell->parent,
wp->layout_cell->parent->type);
} else {
/*
* No tiled panes at all: make this pane the sole tiled pane
@@ -325,11 +337,19 @@ do_tile_pane(struct window *w, struct window_pane *wp, struct cmdq_item *item)
layout_make_leaf(lc, wp);
}
/*
* If the pane was minimised while floating, record its new tiled cell
* as the saved cell so unminimise can restore it correctly.
*/
if (was_minimised)
wp->saved_layout_cell = wp->layout_cell;
wp->flags &= ~PANE_FLOATING;
TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
window_set_active_pane(w, wp, 1);
if (!(wp->flags & PANE_MINIMISED))
window_set_active_pane(w, wp, 1);
if (w->layout_root != NULL)
layout_fix_offsets(w);

147
layout.c
View File

@@ -46,6 +46,9 @@ static int layout_set_size_check(struct window *, struct layout_cell *,
enum layout_type, int);
static void layout_resize_child_cells(struct window *,
struct layout_cell *);
static struct layout_cell *layout_active_neighbour(struct layout_cell *, int);
void layout_redistribute_cells(struct window *, struct layout_cell *,
enum layout_type);
struct layout_cell *
layout_create_cell(struct layout_cell *lcparent)
@@ -524,6 +527,92 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc,
}
}
/*
* Return the nearest sibling of lc that is not a minimised WINDOWPANE leaf,
* walking forward (forward=1) or backward (forward=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 forward)
{
struct layout_cell *lcother;
if (forward)
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_MINIMISED))
return (lcother); /* visible leaf */
/* minimised leaf — keep walking */
if (forward)
lcother = TAILQ_NEXT(lcother, entry);
else
lcother = TAILQ_PREV(lcother, layout_cells, entry);
}
return (NULL);
}
/*
* Redistribute space equally among all visible (non-minimised WINDOWPANE)
* children of lcparent in the given direction. Minimised WINDOWPANE leaves
* are skipped; their stored sizes are left untouched. Container children
* have their own children resized proportionally via layout_resize_child_cells.
*
* If all children happen to be minimised (n==0), nothing is done.
*/
void
layout_redistribute_cells(struct window *w, struct layout_cell *lcparent,
enum layout_type type)
{
struct layout_cell *lc;
u_int n, total, each, rem, i, target;
/* Count visible cells at this level. */
n = 0;
TAILQ_FOREACH(lc, &lcparent->cells, entry) {
if (lc->type == LAYOUT_WINDOWPANE &&
lc->wp != NULL &&
(lc->wp->flags & PANE_MINIMISED))
continue;
n++;
}
if (n == 0)
return;
total = (type == LAYOUT_LEFTRIGHT) ? lcparent->sx : lcparent->sy;
if (total + 1 < n) /* can't fit even the minimum borders */
return;
/*
* each * n + (n-1) borders = total
* → each = (total - (n-1)) / n, rem = (total - (n-1)) % n
* The first `rem` visible cells get (each+1) to consume the remainder.
*/
each = (total - (n - 1)) / n;
rem = (total - (n - 1)) % n;
i = 0;
TAILQ_FOREACH(lc, &lcparent->cells, entry) {
if (lc->type == LAYOUT_WINDOWPANE &&
lc->wp != NULL &&
(lc->wp->flags & PANE_MINIMISED))
continue;
target = each + (i < rem ? 1 : 0);
if (type == LAYOUT_LEFTRIGHT)
lc->sx = target;
else
lc->sy = target;
if (lc->type != LAYOUT_WINDOWPANE)
layout_resize_child_cells(w, lc);
i++;
}
}
/* Destroy a cell and redistribute the space in tiled cells. */
void
layout_destroy_cell(struct window *w, struct layout_cell *lc,
@@ -546,10 +635,11 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc,
/* In tiled layouts, merge the space into the previous or next cell. */
if (lcparent->type != LAYOUT_FLOATING) {
if (lc == TAILQ_FIRST(&lcparent->cells))
lcother = TAILQ_NEXT(lc, entry);
else
lcother = TAILQ_PREV(lc, layout_cells, entry);
int forward;
forward = (lc == TAILQ_FIRST(&lcparent->cells)) ? 1 : 0;
lcother = layout_active_neighbour(lc, forward);
if (lcother == NULL)
lcother = layout_active_neighbour(lc, !forward);
if (lcother != NULL && lcparent->type == LAYOUT_LEFTRIGHT)
layout_resize_adjust(w, lcother, lcparent->type, lc->sx + 1);
else if (lcother != NULL)
@@ -574,6 +664,19 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc,
lc->parent = lcparent->parent;
if (lc->parent == NULL) {
lc->xoff = 0; lc->yoff = 0;
/*
* If the sole remaining child is a minimised
* 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
* unminimise can reclaim the correct amount.
*/
if (lc->type == LAYOUT_WINDOWPANE &&
lc->wp != NULL &&
(lc->wp->flags & PANE_MINIMISED)) {
lc->sx = lcparent->sx;
lc->sy = lcparent->sy;
}
*lcroot = lc;
} else
TAILQ_REPLACE(&lc->parent->cells, lcparent, lc, entry);
@@ -595,11 +698,14 @@ layout_minimise_cell(struct window *w, struct layout_cell *lc)
return;
}
/* Merge the space into the previous or next cell. */
if (lc == TAILQ_FIRST(&lcparent->cells))
lcother = TAILQ_NEXT(lc, entry);
else
lcother = TAILQ_PREV(lc, layout_cells, entry);
/* Merge the space into the nearest non-minimised sibling. */
{
int forward;
forward = (lc == TAILQ_FIRST(&lcparent->cells)) ? 1 : 0;
lcother = layout_active_neighbour(lc, forward);
if (lcother == NULL)
lcother = layout_active_neighbour(lc, !forward);
}
if (lcother != NULL && lcparent->type == LAYOUT_LEFTRIGHT)
layout_resize_adjust(w, lcother, lcparent->type, lc->sx + 1);
else if (lcother != NULL)
@@ -626,26 +732,21 @@ layout_minimise_cell(struct window *w, struct layout_cell *lc)
void
layout_unminimise_cell(struct window *w, struct layout_cell *lc)
{
struct layout_cell *lcother, *lcparent;
struct layout_cell *lcparent;
if (lc == NULL)
return;
lcparent = lc->parent;
if (lcparent == NULL) {
if (lcparent == NULL || lcparent->type == LAYOUT_FLOATING)
return;
}
/* In tiled layouts, merge the space into the previous or next cell. */
if (lcparent->type != LAYOUT_FLOATING) {
if (lc == TAILQ_FIRST(&lcparent->cells))
lcother = TAILQ_NEXT(lc, entry);
else
lcother = TAILQ_PREV(lc, layout_cells, entry);
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));
}
/*
* Redistribute the parent's space equally among all visible (non-
* minimised) children, including lc which has just been unminimised.
* This ensures every pane at this level gets an equal share rather
* than one pane losing most of its space to the restored pane.
*/
layout_redistribute_cells(w, lcparent, lcparent->type);
}
void

28
tmux.1
View File

@@ -3192,6 +3192,23 @@ or
(time).
.Fl r
reverses the sort order.
.Tg minp
.It Xo Ic minimise\-pane
.Op Fl a
.Op Fl t Ar target\-pane
.Xc
.D1 Pq alias: Ic minimize\-pane
Hide
.Ar target\-pane
from the tiled layout without closing it.
The pane continues to run but is no longer visible and does not occupy any
screen space.
Minimised panes are shown in the status line and can be restored with
.Ic unminimise\-pane .
With
.Fl a ,
all visible panes in the window are minimised.
The pane must not already be minimised.
.Tg movep
.It Xo Ic move\-pane
.Op Fl bdfhv
@@ -3833,6 +3850,17 @@ a subsequent
.Ic float\-pane
command with no geometry options.
The pane must be floating and the window must not be zoomed.
.Tg unminp
.It Xo Ic unminimise\-pane
.Op Fl t Ar target\-pane
.Xc
.D1 Pq alias: Ic unminimize\-pane
Restore a minimised
.Ar target\-pane
to the tiled layout.
Space is redistributed equally among all visible panes at the same layout
level after the pane is restored.
The pane must be minimised.
.Tg unlinkw
.It Xo Ic unlink\-window
.Op Fl k

2
tmux.h
View File

@@ -3445,6 +3445,8 @@ void layout_destroy_cell(struct window *, struct layout_cell *,
struct layout_cell **);
void layout_minimise_cell(struct window *, struct layout_cell *);
void layout_unminimise_cell(struct window *, struct layout_cell *);
void layout_redistribute_cells(struct window *, struct layout_cell *,
enum layout_type);
void layout_resize_layout(struct window *, struct layout_cell *,
enum layout_type, int, int);
struct layout_cell *layout_search_by_border(struct layout_cell *, u_int, u_int);