From 42f6f3ca264b4065604ef119e82c84e71e12b720 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Thu, 1 Oct 2026 17:16:42 +0100 Subject: [PATCH] Make show desktop a cycle and ignore hidden zooms in window_zoomed_flag. Co-Authored-By: Claude Sonnet 5.5 --- cmd-resize-pane.c | 24 ++++++++++++----- format.c | 2 +- regress/pane-hidden.sh | 59 ++++++++++++++++++++++++++++++++++++++++-- regress/pane-states.sh | 43 +++++++++++++++++++++++++----- tmux.1 | 2 +- tmux.h | 1 + window.c | 15 ++++++++++- 7 files changed, 127 insertions(+), 19 deletions(-) diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index 9455c7687..d7f7ae445 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -53,19 +53,29 @@ const struct cmd_entry cmd_resize_pane_entry = { /* * Show the desktop: hide every pane above the layout, remembering which, or if - * any were hidden like that show just those again. + * any were hidden like that show just those again: floating panes first, then + * zoomed panes. */ static void cmd_resize_pane_desktop(struct window *w) { struct window_pane *wp; - int shown = 0; + int shown = 0, zoomed; - TAILQ_FOREACH(wp, &w->panes, entry) { - if (~wp->flags & PANE_HIDDENALL) - continue; - window_show_pane(wp); - shown = 1; + /* + * Panes hidden by an earlier call are marked. Show the marked panes + * that are not zoomed first, then the zoomed ones on the next call; if + * none are left hide every floating and zoomed pane. + */ + for (zoomed = 0; zoomed <= 1 && !shown; zoomed++) { + TAILQ_FOREACH(wp, &w->panes, entry) { + if (~wp->flags & PANE_HIDDENALL) + continue; + if (zoomed != ((wp->flags & PANE_ZOOMED) != 0)) + continue; + window_show_pane(wp); + shown = 1; + } } if (shown) return; diff --git a/format.c b/format.c index 7ea836f5e..c726630ee 100644 --- a/format.c +++ b/format.c @@ -3393,7 +3393,7 @@ static void * format_cb_window_zoomed_flag(struct format_tree *ft) { if (ft->w != NULL) { - if (ft->w->flags & WINDOW_ZOOMED) + if (window_has_visible_zoom(ft->w)) return (xstrdup("1")); return (xstrdup("0")); } diff --git a/regress/pane-hidden.sh b/regress/pane-hidden.sh index bee110a43..bc89fbdcd 100644 --- a/regress/pane-hidden.sh +++ b/regress/pane-hidden.sh @@ -159,8 +159,8 @@ run select-pane -t "$B" hidden "$B" 0 check "$B" '#{pane_active}' 1 -# resize-pane -a -H hides every visible float and zoomed pane and a second -# call shows only those, leaving panes that were already hidden alone. +# resize-pane -a -H hides every visible float and zoomed pane and later calls +# show only those, floats first, leaving panes that were already hidden alone. reset X=$(run new-pane -dPF '#{pane_id}' -t "$A" -x 20 -y 8 -X 8 -Y 3 '') || exit 1 Y=$(run new-pane -dPF '#{pane_id}' -t "$A" -x 20 -y 8 -X 30 -Y 6 '') || exit 1 @@ -179,9 +179,64 @@ run resize-pane -a -H -t "$A" hidden "$B" 1 hidden "$X" 1 run resize-pane -a -H -t "$A" +hidden "$X" 0 +hidden "$B" 1 +run resize-pane -a -H -t "$A" hidden "$B" 0 check "$B" '#{pane_zoomed_flag}' 1 +# With two zoomed panes and a float the calls cycle: hide all, show the float, +# show the zooms, hide all again. Hidden zoomed panes are not a zoomed window. +reset +run resize-pane -Z -t "$A" +run resize-pane -Z -t "$B" +X=$(run new-pane -dPF '#{pane_id}' -t "$A" -x 20 -y 8 -X 8 -Y 3 '') || exit 1 +check "$A" '#{window_zoomed_flag}' 1 +for i in 1 2; do + run resize-pane -a -H -t "$A" + for p in "$A" "$B" "$X"; do + hidden "$p" 1 + done + check "$A" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '0:1' + check "$A" '#{window_flags}' '*' + run resize-pane -a -H -t "$A" + hidden "$X" 0 + hidden "$A" 1 + hidden "$B" 1 + check "$A" '#{window_zoomed_flag}' 0 + run resize-pane -a -H -t "$A" + for p in "$A" "$B" "$X"; do + hidden "$p" 0 + done + check "$A" '#{window_zoomed_flag}:#{window_flags}' '1:*Z' +done + +# A stage with nothing to show is skipped, so every call changes something. +reset +run resize-pane -Z -t "$A" +run resize-pane -a -H -t "$A" +hidden "$A" 1 +run resize-pane -a -H -t "$A" +hidden "$A" 0 +reset +X=$(run new-pane -dPF '#{pane_id}' -t "$A" -x 20 -y 8 -X 8 -Y 3 '') || exit 1 +run resize-pane -a -H -t "$A" +hidden "$X" 1 +run resize-pane -a -H -t "$A" +hidden "$X" 0 + +# Showing a pane by hand takes it out of the cycle. +reset +run resize-pane -Z -t "$A" +X=$(run new-pane -dPF '#{pane_id}' -t "$A" -x 20 -y 8 -X 8 -Y 3 '') || exit 1 +run resize-pane -a -H -t "$A" +run select-pane -t "$X" +hidden "$X" 0 +hidden "$A" 1 +run resize-pane -a -H -t "$A" +hidden "$A" 0 +hidden "$X" 0 + # Floating and tiling a hidden pane keeps it hidden. reset run resize-pane -H -t "$B" diff --git a/regress/pane-states.sh b/regress/pane-states.sh index 5c49759ed..43780c503 100644 --- a/regress/pane-states.sh +++ b/regress/pane-states.sh @@ -9,7 +9,8 @@ # expectation from the state before. This covers: # - select-pane, including -Z and moving to the next, last or a directional pane; # - resize-pane -Z (toggle a zoom), -H (hide or show), -a -Z (unzoom all) and -# -a -H (show desktop), which is run twice to check it restores the panes; +# -a -H (show desktop), which is run once, twice and three times to check +# the hide, floats and zoomed panes steps; # - break-pane -W and join-pane to float and tile panes; # - split-window, new-pane and kill-pane; # - next-layout, select-layout, rotate-window and resize-window. @@ -19,7 +20,7 @@ # # It also checks properties that must hold after every command: the active pane # is not hidden if any pane is not, there is one active pane, a window with one -# pane is not zoomed, window_zoomed_flag matches the panes and visible panes have +# pane is not zoomed, window_zoomed_flag matches the visible zoomed panes and visible panes have # a size. The status line clicks and the cursor are in other tests. PATH=/bin:/usr/bin @@ -147,7 +148,7 @@ END { for (i = 1; i <= na; i++) { id = aid[i] if (aA[id] == 1) { nact++; act = id } - if (aZ[id]) anyZ = 1 + if (aZ[id] && !aH[id]) anyZ = 1 if (!aH[id]) anyvis = 1 } if (nact != 1) @@ -155,7 +156,7 @@ END { for (i = 1; i <= na; i++) { id = aid[i] if (aWZ[id] != anyZ) - fail("window_zoomed_flag " aWZ[id] " with zoomed panes " anyZ) + fail("window_zoomed_flag " aWZ[id] " with visible zoomed panes " anyZ) if (!aH[id] && (aW[id] < 1 || aT[id] < 1)) fail("pane " id " is " aW[id] "x" aT[id]) } @@ -209,8 +210,31 @@ END { fail("pane " id " hidden " bH[id] " floating " bF[id] \ " zoomed " bZ[id] " became hidden " aH[id]) } - } else if (op == "desktop2") { - sameF(""); sameZ(""); sameH("") + } else if (op == "desktop2" || op == "desktop3") { + # The first call hides every floating and zoomed pane, the + # second shows the floats and the third the zoomed panes. A + # call with nothing to show is skipped, so with only one kind + # the second call restores them and the third hides them again. + nf = 0; nz = 0 + for (i = 1; i <= nb; i++) { + id = bid[i] + if (bH[id]) continue + if (bZ[id]) nz++ + else if (bF[id]) nf++ + } + sameF(""); sameZ("") + for (i = 1; i <= nb; i++) { + id = bid[i] + want = bH[id] + if (!bH[id] && bZ[id]) + want = (op == "desktop2") ? (nf > 0) : (nf == 0 || nz == 0) + else if (!bH[id] && bF[id]) + want = (op == "desktop3") ? (nf == 0 || nz == 0) : 0 + if (aH[id] != want) + fail("pane " id " hidden " bH[id] " floating " bF[id] \ + " zoomed " bZ[id] " became hidden " aH[id] \ + " after " op " (floats " nf ", zooms " nz ")") + } } else if (op == "unzoomall") { sameF(""); sameH(""); noZoom() } else if (op == "break") { @@ -349,6 +373,11 @@ run_one() tm resize-pane -a -H -t %0 || rc=$? tm resize-pane -a -H -t %0 || rc=$? ;; + desktop3) + tm resize-pane -a -H -t %0 || rc=$? + tm resize-pane -a -H -t %0 || rc=$? + tm resize-pane -a -H -t %0 || rc=$? + ;; unzoomall) tm resize-pane -a -Z -t %0 || rc=$? ;; nextlayout) tm next-layout -t %0 || rc=$? ;; tiled) tm select-layout -t %0 tiled || rc=$? ;; @@ -378,7 +407,7 @@ run_one() } PER_PANE="select selectZ zoom hide break join kill split newpane" -GLOBAL="desktop desktop2 unzoomall nextlayout tiled rotate nextpane lastpane \ +GLOBAL="desktop desktop2 desktop3 unzoomall nextlayout tiled rotate nextpane lastpane \ left right up down resize" # Two panes: every state, every operation. diff --git a/tmux.1 b/tmux.1 index c4d19774c..bf734ba61 100644 --- a/tmux.1 +++ b/tmux.1 @@ -7589,7 +7589,7 @@ The following variables are available, where appropriate: .It Li "window_start_flag" Ta "" Ta "1 if window has the lowest index" .It Li "window_visible_layout" Ta "" Ta "Window layout description, respecting zoomed window panes" .It Li "window_width" Ta "" Ta "Width of window" -.It Li "window_zoomed_flag" Ta "" Ta "1 if any pane in the window is zoomed" +.It Li "window_zoomed_flag" Ta "" Ta "1 if any pane in the window is zoomed and not hidden" .It Li "wrap_flag" Ta "" Ta "Pane wrap flag" .El .Sh STYLES diff --git a/tmux.h b/tmux.h index ee679163a..48d577c12 100644 --- a/tmux.h +++ b/tmux.h @@ -3743,6 +3743,7 @@ int window_zoom(struct window_pane *); int window_unzoom_pane(struct window_pane *, int); int window_unzoom(struct window *, int); struct window_pane *window_zoomed_pane(struct window *); +int window_has_visible_zoom(struct window *); void window_zoom_move(struct window_pane *, struct window_pane *); void window_lost_pane(struct window *, struct window_pane *); void window_remove_pane(struct window *, struct window_pane *); diff --git a/window.c b/window.c index 478b80dce..d1eb4ee13 100644 --- a/window.c +++ b/window.c @@ -1174,6 +1174,19 @@ window_lower_pane(struct window_pane *wp) redraw_invalidate_scene(w); } +/* Is any pane that is not hidden zoomed? */ +int +window_has_visible_zoom(struct window *w) +{ + struct window_pane *wp; + + TAILQ_FOREACH(wp, &w->panes, entry) { + if ((wp->flags & PANE_ZOOMED) && (~wp->flags & PANE_HIDDEN)) + return (1); + } + return (0); +} + /* Set or clear WINDOW_ZOOMED depending on whether any pane is zoomed. */ static void window_update_zoomed(struct window *w) @@ -1570,7 +1583,7 @@ window_printable_flags(struct winlink *wl, int escape) flags[pos++] = 'M'; if (wl->window->modal != NULL) flags[pos++] = 'O'; - if (wl->window->flags & WINDOW_ZOOMED) + if (window_has_visible_zoom(wl->window)) flags[pos++] = 'Z'; flags[pos] = '\0'; return (flags);