From 0ce3f20a2dc77719b81f300484e2e17625f8e069 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Thu, 1 Oct 2026 17:56:43 +0100 Subject: [PATCH] Hide the zoomed panes when a covered tiled pane is activated. Co-Authored-By: Claude Sonnet 5.5 --- cmd-resize-pane.c | 12 ++++++++- cmd-rotate-window.c | 4 +-- cmd-select-pane.c | 17 ++++++------ cmd-switch-client.c | 2 +- regress/floating-pane-zoom-stack.sh | 21 ++++++++++++--- regress/pane-hidden.sh | 42 +++++++++++++++++++++++++++++ regress/pane-ops.sh | 21 ++++++++++++--- regress/pane-states.sh | 31 ++++++++++++++++----- regress/window-ops.sh | 10 +++++-- window.c | 36 +++++++++++++++++++++---- 10 files changed, 164 insertions(+), 32 deletions(-) diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index d7f7ae445..6f04c2fae 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -77,8 +77,18 @@ cmd_resize_pane_desktop(struct window *w) shown = 1; } } - if (shown) + if (shown) { + /* Do not leave the active pane covered by a pane just shown. */ + if (!window_pane_is_visible(w->active)) { + TAILQ_FOREACH(wp, &w->z_index, zentry) { + if (window_pane_is_visible(wp)) + break; + } + if (wp != NULL) + window_set_active_pane(w, wp, 1); + } return; + } TAILQ_FOREACH(wp, &w->panes, entry) { if (wp == w->modal || (wp->flags & PANE_HIDDEN)) diff --git a/cmd-rotate-window.c b/cmd-rotate-window.c index 3e112bbf2..c47814d85 100644 --- a/cmd-rotate-window.c +++ b/cmd-rotate-window.c @@ -121,10 +121,10 @@ cmd_rotate_window_exec(struct cmd *self, struct cmdq_item *item) if (active != -1) { wp = rotated[active]; - window_set_active_pane(w, wp, 1); - cmd_find_from_winlink_pane(current, wl, wp, 0); if (zwp != NULL) window_zoom_move(zwp, wp); + window_set_active_pane(w, wp, 1); + cmd_find_from_winlink_pane(current, wl, wp, 0); } redraw_invalidate_scene(w); server_redraw_window(w); diff --git a/cmd-select-pane.c b/cmd-select-pane.c index 6c9185a6c..5a1ff3d11 100644 --- a/cmd-select-pane.c +++ b/cmd-select-pane.c @@ -193,14 +193,15 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) if (!visible && Zflag) zwp = window_zoomed_pane(w); window_redraw_active_switch(w, lastwp); - if (window_set_active_pane(w, lastwp, 1)) { - cmd_find_from_winlink(current, wl, 0); - cmd_select_pane_redraw(w); - } if (zwp != NULL) { window_zoom_move(zwp, lastwp); server_redraw_window(w); } + if (window_set_active_pane(w, lastwp, 1) || + zwp != NULL) { + cmd_find_from_winlink(current, wl, 0); + cmd_select_pane_redraw(w); + } } return (CMD_RETURN_NORMAL); } @@ -273,14 +274,14 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) if (!visible && Zflag) zwp = window_zoomed_pane(w); window_redraw_active_switch(w, wp); - if (window_set_active_pane(w, wp, 1)) - cmd_find_from_winlink_pane(current, wl, wp, 0); - cmdq_insert_hook(s, item, current, "after-select-pane"); - cmd_select_pane_redraw(w); if (zwp != NULL) { window_zoom_move(zwp, wp); server_redraw_window(w); } + if (window_set_active_pane(w, wp, 1) || zwp != NULL) + cmd_find_from_winlink_pane(current, wl, wp, 0); + cmdq_insert_hook(s, item, current, "after-select-pane"); + cmd_select_pane_redraw(w); return (CMD_RETURN_NORMAL); } diff --git a/cmd-switch-client.c b/cmd-switch-client.c index 204725429..04c34785e 100644 --- a/cmd-switch-client.c +++ b/cmd-switch-client.c @@ -147,11 +147,11 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item) if (!visible && Zflag) zwp = window_zoomed_pane(w); window_redraw_active_switch(w, wp); - window_set_active_pane(w, wp, 1); if (zwp != NULL) { window_zoom_move(zwp, wp); server_redraw_window(w); } + window_set_active_pane(w, wp, 1); } if (wl != NULL) { session_set_current(s, wl); diff --git a/regress/floating-pane-zoom-stack.sh b/regress/floating-pane-zoom-stack.sh index 0068cc6c4..945792e3f 100644 --- a/regress/floating-pane-zoom-stack.sh +++ b/regress/floating-pane-zoom-stack.sh @@ -144,17 +144,32 @@ zoomed "$Y" 1 zoomed "$A" 1 window_zoomed "$X" 1 -# Activating a tiled pane that is not zoomed unzooms every pane but the -# floats stay floats. +# Activating a tiled pane that is not zoomed hides the zoomed panes, as the +# desktop does, and shows the tiles. They stay zoomed, and floats that are not +# zoomed stay where they are. run select-pane -t "$B" check "$B" '#{pane_active}' 1 -for p in "$A" "$B" "$X" "$Y"; do +for p in "$A" "$Y"; do + zoomed "$p" 1 + check "$p" '#{pane_hidden_flag}' 1 +done +for p in "$B" "$X"; do zoomed "$p" 0 + check "$p" '#{pane_hidden_flag}' 0 done window_zoomed "$B" 0 check "$X" '#{pane_floating_flag}' 1 check "$Y" '#{pane_floating_flag}' 1 +# The desktop shows them again. +run resize-pane -a -H -t "$A" +for p in "$A" "$Y"; do + zoomed "$p" 1 + check "$p" '#{pane_hidden_flag}' 0 +done +window_zoomed "$B" 1 +run resize-pane -a -Z -t "$A" + # Activating a tiled pane that is itself zoomed does not unzoom it. run resize-pane -Z -t "$A" run select-pane -t "$A" diff --git a/regress/pane-hidden.sh b/regress/pane-hidden.sh index bc89fbdcd..53d4e1e51 100644 --- a/regress/pane-hidden.sh +++ b/regress/pane-hidden.sh @@ -237,6 +237,48 @@ run resize-pane -a -H -t "$A" hidden "$A" 0 hidden "$X" 0 +# Activating a tiled pane that is covered by zoomed panes hides them, as the +# desktop does: they stay zoomed, the tiles are shown and the pane is active. +reset +C=$(run split-window -dPF '#{pane_id}') || exit 1 +run resize-pane -Z -t "$A" +run resize-pane -Z -t "$B" +run select-pane -t "$C" +check "$C" '#{pane_active}' 1 +for p in "$A" "$B"; do + hidden "$p" 1 + check "$p" '#{pane_zoomed_flag}' 1 +done +hidden "$C" 0 +check "$C" '#{window_zoomed_flag}' 0 +# The last pane brings back the one that was zoomed. +run select-pane -l +check "$B" '#{pane_active}:#{pane_hidden_flag}:#{window_zoomed_flag}' '1:0:1' +# The desktop shows the others, and the active pane is not left covered. +run select-pane -t "$C" +run resize-pane -a -H -t "$A" +for p in "$A" "$B"; do + hidden "$p" 0 +done +check "$A" '#{window_zoomed_flag}' 1 +visible=$(run list-panes -F '#{pane_active}#{pane_zoomed_flag}' | grep -c '^11$') +[ "$visible" = 1 ] || fail "active pane is covered after the desktop" + +# select-pane -Z moves the zoom to a covered tiled pane without hiding the old +# one, and so do last-pane -Z and rotate-window -Z. +reset +run resize-pane -Z -t "$A" +run select-pane -Z -t "$B" +check "$B" '#{pane_zoomed_flag}:#{pane_active}' '1:1' +check "$A" '#{pane_zoomed_flag}:#{pane_hidden_flag}' '0:0' +run select-pane -Z -t "$A" +run select-pane -Z -l +check "$B" '#{pane_zoomed_flag}:#{pane_hidden_flag}' '1:0' +check "$A" '#{pane_zoomed_flag}:#{pane_hidden_flag}' '0:0' +run rotate-window -Z +check "$A" '#{pane_hidden_flag}' 0 +check "$B" '#{pane_hidden_flag}' 0 + # Floating and tiling a hidden pane keeps it hidden. reset run resize-pane -H -t "$B" diff --git a/regress/pane-ops.sh b/regress/pane-ops.sh index 60696234a..85d6d667b 100644 --- a/regress/pane-ops.sh +++ b/regress/pane-ops.sh @@ -320,7 +320,7 @@ check_ok resize-pane -Z -t "$p0" check_fmt "$p0" '#{window_zoomed_flag}' '0' # Splitting while zoomed with -d leaves the zoom alone; without -d the new -# pane is active and covered, so selecting it unzooms. +# pane is active and covered, so selecting it hides the zoomed pane. check_ok resize-pane -Z -t "$p0" check_fmt 'P:0' '#{window_zoomed_flag}' '1' check_ok split-window -d -v -t "$p0" @@ -334,16 +334,25 @@ check_fmt 'P:0' '#{window_zoomed_flag}' '0' check_ok resize-pane -Z -t "$p0" check_ok split-window -v -t "$p0" check_fmt 'P:0' '#{window_zoomed_flag}' '0' +check_fmt "$p0" '#{pane_zoomed_flag}:#{pane_hidden_flag}' '1:1' p6=$($TMUX display-message -p -t P:0 '#{pane_id}') check_ok kill-pane -t "$p6" +check_ok resize-pane -a -H -t "$p0" +check_ok resize-pane -a -Z -t "$p0" # Zoom and unzoom preserve the exact tiled layout. Selecting another pane -# without -Z unzooms, while -Z transfers zoom to the selected pane. +# without -Z hides the zoomed pane until the desktop shows it, while -Z +# transfers zoom to the selected pane. layout=$(pane_layout P:0) check_ok select-pane -t "$p0" check_ok resize-pane -Z -t "$p0" check_ok select-pane -t "$p2" check_fmt "$p2" '#{window_zoomed_flag}:#{pane_active}' '0:1' +check_fmt "$p0" '#{pane_zoomed_flag}:#{pane_hidden_flag}' '1:1' +check_ok resize-pane -a -H -t "$p0" +check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \ + '1:1:1' +check_ok resize-pane -a -Z -t "$p0" check_layout P:0 "$layout" check_ok select-pane -t "$p0" @@ -355,7 +364,7 @@ check_ok resize-pane -Z -t "$p2" check_layout P:0 "$layout" # Directional selection temporarily restores the full layout to find its -# neighbour, then follows the same unzoom or -Z transfer rules. +# neighbour, then follows the same hide or -Z transfer rules. check_ok select-pane -t "$p0" check_ok resize-pane -Z -t "$p0" check_ok select-pane -t "$p0" @@ -363,6 +372,9 @@ check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \ '1:1:1' check_ok select-pane -D -t "$p0" check_fmt "$p2" '#{window_zoomed_flag}:#{pane_active}' '0:1' +check_fmt "$p0" '#{pane_zoomed_flag}:#{pane_hidden_flag}' '1:1' +check_ok resize-pane -a -H -t "$p0" +check_ok resize-pane -a -Z -t "$p0" check_ok select-pane -t "$p0" check_ok resize-pane -Z -t "$p0" @@ -379,6 +391,9 @@ check_ok resize-pane -Z -t "$p2" check_ok select-pane -l -t P:0 check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \ '0:0:1' +check_fmt "$p2" '#{pane_zoomed_flag}:#{pane_hidden_flag}' '1:1' +check_ok resize-pane -a -H -t "$p0" +check_ok resize-pane -a -Z -t "$p0" check_ok select-pane -t "$p0" check_ok select-pane -t "$p2" diff --git a/regress/pane-states.sh b/regress/pane-states.sh index 43780c503..eeffbdf8f 100644 --- a/regress/pane-states.sh +++ b/regress/pane-states.sh @@ -93,6 +93,21 @@ function noZoom( i) fail("pane " aid[i] " is still zoomed") } } +function hideZoomed(t, i, id, want) +{ + sameZ("") + for (i = 1; i <= nb; i++) { + id = bid[i] + if (id == t || !(id in ina)) + continue + want = (bH[id] || bZ[id]) ? 1 : 0 + if (aH[id] != want) + fail("pane " id " zoomed " bZ[id] " hidden " bH[id] \ + " became hidden " aH[id]) + } + if (aH[t]) + fail("pane " t " is still hidden") +} function covered(t, i, q) { if (bF[t] || bZ[t]) @@ -177,8 +192,9 @@ END { if (op == "select") { if (aA[T] != 1) fail("not active") if (aH[T]) fail("still hidden") - if (covered(T)) noZoom(); else sameZ("") - sameF(""); sameH(T) + if (covered(T)) hideZoomed(T) + else { sameZ(""); sameH(T) } + sameF("") } else if (op == "zoom") { sameF("") if (bZ[T]) { @@ -195,12 +211,12 @@ END { if (bH[T]) { if (aH[T]) fail("not shown") if (aA[T] != 1) fail("not active when shown") - if (covered(T)) noZoom(); else sameZ("") + if (covered(T)) hideZoomed(T) + else { sameZ(""); sameH(T) } } else { if (!aH[T]) fail("not hidden") - sameZ("") + sameZ(""); sameH(T) } - sameH(T) } else if (op == "desktop") { sameF(""); sameZ("") for (i = 1; i <= nb; i++) { @@ -267,11 +283,12 @@ END { sameF(""); sameH(""); noZoom() } else { # Moving around or changing layout: floating panes stay floating - # and nothing is hidden. + # and only a zoomed pane is hidden, when a covered pane is made + # active. sameF("") for (i = 1; i <= nb; i++) { id = bid[i] - if (id in ina && aH[id] > bH[id]) + if (id in ina && aH[id] > bH[id] && !bZ[id]) fail("pane " id " was hidden by this") } } diff --git a/regress/window-ops.sh b/regress/window-ops.sh index c1bbce674..b1704de18 100644 --- a/regress/window-ops.sh +++ b/regress/window-ops.sh @@ -337,14 +337,20 @@ check_fmt 'R:0' '#{pane_index}:#{pane_id}' "0:$p1" check_ok rotate-window -D -t R:0 check_fmt 'R:0' '#{pane_index}:#{pane_id}' "0:$p0" -# Rotation without -Z unzooms. With -Z it preserves zoom and transfers it to -# the pane which arrives at the active position. +# Rotation without -Z hides the zoomed pane until the desktop shows it. With -Z +# it preserves zoom and transfers it to the pane which arrives at the active +# position. layout=$($TMUX display-message -p -t R:0 '#{window_layout}') check_ok resize-pane -Z -t "$p0" check_ok rotate-window -U -t R:0 check_fmt "$p1" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \ '0:0:1' +check_fmt "$p0" '#{pane_zoomed_flag}:#{pane_hidden_flag}' '1:1' +check_ok resize-pane -a -H -t "$p0" +check_ok resize-pane -a -Z -t "$p0" check_ok rotate-window -D -t R:0 +check_ok select-pane -t "$p1" +check_ok select-pane -t "$p0" check_fmt 'R:0' '#{window_layout}' "$layout" check_ok resize-pane -Z -t "$p0" diff --git a/window.c b/window.c index d1eb4ee13..7fd5398cf 100644 --- a/window.c +++ b/window.c @@ -75,6 +75,8 @@ static void window_pane_free(struct window_pane *); static void window_pane_scrollbar_timer(int, short, void *); static int window_activate_pane(struct window *, struct window_pane *, int, int); +static void window_hide_one(struct window_pane *, int); +static void window_hide_zoomed(struct window *); static void window_pane_full_size_offset(struct window_pane *, int *, int *, u_int *, u_int *); @@ -775,14 +777,14 @@ window_activate_pane(struct window *w, struct window_pane *wp, int notify, return (0); if (!window_pane_is_visible(wp)) { /* - * The pane is covered by a zoomed pane. A tiled pane that is - * not zoomed can only be reached by unzooming (any floats - * stay); anything else can be raised above the zoom. + * The pane is covered by a zoomed pane. Raise it above the zoom + * if it can be, otherwise (a tiled pane that is not zoomed) hide + * the zoomed panes, as the desktop does, to show the tiles. */ if (window_pane_is_raised(wp)) window_raise_pane(wp); else - window_unzoom(w, 1); + window_hide_zoomed(w); } else if (raise) { /* Raise the pane if the option says to. */ switch (options_get_number(w->options, "pane-raise-on-focus")) { @@ -1041,6 +1043,30 @@ window_count_tiled_siblings(struct layout_cell *lc) */ void window_hide_pane(struct window_pane *wp) +{ + window_hide_one(wp, 1); +} + +/* + * Hide every zoomed pane that is not hidden, marking them as the desktop does so + * that it shows them again. The focus is left for the caller to move. + */ +static void +window_hide_zoomed(struct window *w) +{ + struct window_pane *wp; + + TAILQ_FOREACH(wp, &w->panes, entry) { + if ((wp->flags & PANE_ZOOMED) && (~wp->flags & PANE_HIDDEN)) { + window_hide_one(wp, 0); + wp->flags |= PANE_HIDDENALL; + } + } +} + +/* Hide a pane, moving the focus away from it if it is active and asked to. */ +static void +window_hide_one(struct window_pane *wp, int refocus) { struct window *w = wp->window; struct layout_cell *lc = wp->layout_cell; @@ -1068,7 +1094,7 @@ window_hide_pane(struct window_pane *wp) layout_fix_panes(w, NULL); /* Move the focus to the last used pane or the top one that is seen. */ - if (wp == w->active) { + if (refocus && wp == w->active) { TAILQ_FOREACH(wp1, &w->last_panes, sentry) { if (wp1 != wp && window_pane_is_visible(wp1)) break;