From d8aeec3058637818b5e730934ff7f181c704b4df Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Sun, 11 Oct 2026 11:16:10 +0900 Subject: [PATCH] Preserve tmux window zoom when opening a floating pane - Use new-pane -A on tmux 3.8+ to keep floating pane above zoomed pane - Detect -A from new-pane usage; keep unzoom fallback for 3.7 Fix #4927 Fix junegunn/fzf.vim#1625 Co-authored-by: Jere Viikari --- CHANGELOG.md | 2 ++ src/tmux.go | 39 +++++++++++++++++++++++---------------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ddc570e..1296e845 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ CHANGELOG - Fixed `focus` event not triggered after `reload` when the new item is at the same position as the previous one (#4930) - With `--id-nth`, the event is not triggered if the focus moves directly from an item to another item with the same key - A `focus` binding that runs `reload` now triggers itself again after each reload, unless `--id-nth` is set and the key does not change +- Fixed `--popup` unzooming the window when opened over a zoomed pane on tmux 3.8 or above (#4927, junegunn/fzf.vim#1625) + - On tmux 3.7, the window is still unzoomed, as tmux 3.7b crashes when a floating pane is created over a zoomed window 0.74.4 ------ diff --git a/src/tmux.go b/src/tmux.go index 176c9f45..c9c44ef0 100644 --- a/src/tmux.go +++ b/src/tmux.go @@ -5,14 +5,19 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "strings" "github.com/junegunn/fzf/src/tui" ) +// The flag group of new-pane usage includes -A (tmux 3.8 or above) +var tmuxOverZoomFlag = regexp.MustCompile(`new-pane.*\[-[[:alpha:]]*A`) + // Returns the size of the current window if the tmux server supports -// floating panes (tmux 3.7 or above) -func tmuxFloatingPaneInfo() (int, int, bool) { +// floating panes (tmux 3.7 or above), and whether a floating pane can stay +// above a zoomed pane +func tmuxFloatingPaneInfo() (int, int, bool, bool) { // TMUX_PANE is not set when fzf is not started from within a pane // (e.g. 'bind-key 0 run-shell "fzf --popup"'). Do not use a floating // pane there; a blocking run-shell suspends key processing for the @@ -25,7 +30,7 @@ func tmuxFloatingPaneInfo() (int, int, bool) { // https://github.com/tmux/tmux/issues/5384 target := os.Getenv("TMUX_PANE") if target == "" { - return 0, 0, false + return 0, 0, false, false } // A single invocation for both checks. Cannot rely on the exit status; // tmux versions before 3.7 exit normally with empty output for an @@ -33,17 +38,17 @@ func tmuxFloatingPaneInfo() (int, int, bool) { out, err := exec.Command("tmux", "display-message", "-p", "-t", target, "#{window_width} #{window_height}", ";", "list-commands", "new-pane").Output() if err != nil || !strings.Contains(string(out), "new-pane") { - return 0, 0, false + return 0, 0, false, false } var width, height int if _, err := fmt.Sscanf(string(out), "%d %d", &width, &height); err != nil { - return 0, 0, false + return 0, 0, false, false } // The window is too small to fit a floating pane of the minimum size if width < 3 || height < 3 { - return 0, 0, false + return 0, 0, false, false } - return width, height, true + return width, height, tmuxOverZoomFlag.Match(out), true } // tmux ends a command at an argument ending in ';', so a trailing one is @@ -85,7 +90,7 @@ func tmuxDim(spec sizeSpec, window int) int { return max(3, min(dim, window)) } -func runTmuxFloatingPane(argStr string, dir string, windowWidth int, windowHeight int, opts *Options) (int, error) { +func runTmuxFloatingPane(argStr string, dir string, windowWidth int, windowHeight int, overZoom bool, opts *Options) (int, error) { // Unlike display-popup, the size of a floating pane does not account for // the border around it, and the position is that of the content area. To // stay consistent with popups, treat the requested size as the total @@ -166,13 +171,15 @@ func runTmuxFloatingPane(argStr string, dir string, windowWidth int, windowHeigh } paneCmd := fmt.Sprintf("%s%s %s; echo $? > %s; tmux wait-for -S %s", setup, escapeSingleQuote(sh), escapeSingleQuote(temp), code, signal) - // Unzoom the window first; creating a floating pane over a zoomed - // window crashes the tmux server on 3.7b, and newer versions of - // tmux unzoom the window anyway. target := os.Getenv("TMUX_PANE") - newPane := fmt.Sprintf( - "tmux if -F -t %s '#{window_zoomed_flag}' %s ';' new-pane -P -F '#{pane_id}' -t %s -c %s -x %d -y %d -X %d -Y %d %s -c %s", - escapeSingleQuote(target), escapeSingleQuote("resize-pane -Z -t "+target), + newPane := "tmux new-pane -A" + if !overZoom { + // Unzoom the window first; creating a floating pane over a + // zoomed window crashes the tmux server on 3.7b + newPane = fmt.Sprintf("tmux if -F -t %s '#{window_zoomed_flag}' %s ';' new-pane", + escapeSingleQuote(target), escapeSingleQuote("resize-pane -Z -t "+target)) + } + newPane += fmt.Sprintf(" -P -F '#{pane_id}' -t %s -c %s -x %d -y %d -X %d -Y %d %s -c %s", escapeSingleQuote(target), escapeSingleQuote(dir), width-2, height-2, x, y, escapeSingleQuote(sh), escapeSingleQuote(paneCmd)) @@ -222,10 +229,10 @@ func runTmux(args []string, opts *Options) (int, error) { // popup is used instead so that the fzf-drawn border is the only border // shown; the native border of a tmux floating pane cannot be removed. if nativeBorder(opts) { - if windowWidth, windowHeight, ok := tmuxFloatingPaneInfo(); ok { + if windowWidth, windowHeight, overZoom, ok := tmuxFloatingPaneInfo(); ok { opts.Tmux.border = true argStr, dir := popupArgStr(args, opts) - return runTmuxFloatingPane(argStr, dir, windowWidth, windowHeight, opts) + return runTmuxFloatingPane(argStr, dir, windowWidth, windowHeight, overZoom, opts) } }