From 3cbe6b31b2b0182a838836644377ea9f6a3e75b4 Mon Sep 17 00:00:00 2001 From: Brad Anderson Date: Sun, 2 Aug 2026 23:07:57 -0700 Subject: [PATCH] Add tmux-fzf for session/window/pane navigation Many concurrent sessions with many panes each make it hard to track what is running where, and moving a pane between windows means join-pane with a hand-typed target, so it rarely gets done. tmux-fzf gives a fuzzy-searchable list of every session, window and pane with a live preview of each, and implements break/join/swap as pane actions -- which is the pane-movement gap. fzf is already in apt.global, arch.global and brew.global, so no new packages are needed. Checked out as a submodule rather than left for TPM to clone on first prefix+I: ~/.tmux symlinks to config/tmux, so a TPM-installed plugin lands inside this repo as untracked files. The submodule pins a version and installs via the existing "git submodule update --init". TPM still sources it from the @plugin line and can still update it. prefix+F is the plugin's own top-level menu. prefix+P/W/S skip straight to the pane, window and session panels. TMUX_FZF_CLIENT pins each action to the client that opened it, as the plugin's own binding does -- run-shell has no client context, so tmux otherwise targets the most recently active client, which is wrong when a second client is attached to the same session. No env-var configuration was needed: preview is already on by default, and the TMUX_FZF_* variables are read from the environment only, so the README's suggestion to set them in .tmux.conf is not valid tmux syntax. Tested against tmux 3.5a and fzf 0.38.0: config parses with no errors, all three bindings register, existing bindings are unaffected, TPM sources the plugin and binds prefix+F, the three scripts pass bash -n, and the plugin's fzf-version detection resolves to fzf-tmux with preview enabled. The interactive picker itself needs a real terminal and is left to manual check. --- .gitmodules | 3 +++ config/tmux.conf | 22 ++++++++++++++++++++++ config/tmux/plugins/tmux-fzf | 1 + 3 files changed, 26 insertions(+) create mode 160000 config/tmux/plugins/tmux-fzf diff --git a/.gitmodules b/.gitmodules index fcfe309..fac4969 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "dependencies/fzf"] path = dependencies/fzf url = https://github.com/junegunn/fzf.git +[submodule "config/tmux/plugins/tmux-fzf"] + path = config/tmux/plugins/tmux-fzf + url = https://github.com/sainnhe/tmux-fzf diff --git a/config/tmux.conf b/config/tmux.conf index 44f7b47..bd562e6 100644 --- a/config/tmux.conf +++ b/config/tmux.conf @@ -47,6 +47,21 @@ bind -r C-l select-window -t :+ bind a setw synchronize-panes +# Fuzzy pickers over every session/window/pane, each with a live preview of +# what it's running (tmux-fzf). prefix+F opens the plugin's own top-level +# menu; these skip straight to the panels used most -- pane.sh is where +# break/join/swap live, i.e. moving a pane into another window without +# hand-typing a target the way join-pane needs. +# +# TMUX_FZF_CLIENT pins the action to the client that opened it, exactly as +# the plugin's own binding does: a command run via run-shell has no client +# context of its own, so tmux otherwise falls back to the most recently +# active client -- the wrong terminal whenever a second client (e.g. a +# browser one over ttyd) is attached to the same session. +bind P run-shell -b "TMUX_FZF_CLIENT='#{client_tty}' ~/.tmux/plugins/tmux-fzf/scripts/pane.sh" +bind W run-shell -b "TMUX_FZF_CLIENT='#{client_tty}' ~/.tmux/plugins/tmux-fzf/scripts/window.sh" +bind S run-shell -b "TMUX_FZF_CLIENT='#{client_tty}' ~/.tmux/plugins/tmux-fzf/scripts/session.sh" + # ---------------------------------------------------------------------------- # Copy mode and mouse # ---------------------------------------------------------------------------- @@ -155,6 +170,13 @@ set -g status-style "#{?client_prefix,#{@status_style_armed},#{@status_style_nor # ---------------------------------------------------------------------------- set -g @plugin 'tmux-plugins/tpm' set -g @plugin 'tmux-plugins/tmux-sensible' +# Checked out as a submodule under config/tmux/plugins rather than left for +# TPM to clone on first prefix+I: ~/.tmux is a symlink to that directory, so +# a TPM-installed plugin lands inside this repo as untracked files. The +# submodule pins a version, installs with the existing "git submodule +# update --init" in install.sh, and keeps git status clean. TPM still sources +# it from the @plugin line below and can still update it. +set -g @plugin 'sainnhe/tmux-fzf' # Local overrides if-shell "[ -f ~/.tmux_local.conf ]" 'source ~/.tmux_local.conf' diff --git a/config/tmux/plugins/tmux-fzf b/config/tmux/plugins/tmux-fzf new file mode 160000 index 0000000..4cbbbbd --- /dev/null +++ b/config/tmux/plugins/tmux-fzf @@ -0,0 +1 @@ +Subproject commit 4cbbbbde54e1b63712d77483b83075fd712d3bb5