From 7ec4c75d864c0dd3ad4c1d5e255843caf2649484 Mon Sep 17 00:00:00 2001 From: dissimulo Date: Tue, 25 Aug 2026 12:09:36 -0700 Subject: [PATCH] tmux: circled window numbers via an async hook, not a per-redraw conditional The old format nested ten #{?#{==:#{window_index},N},...} conditionals inside window-status-format, so tmux re-evaluated all ten for every window on every redraw -- and redraws follow PANE ACTIVITY, not status-interval. A busy pane ran them several times a second. tmux-window-icons stamps each window with its symbol as a window-level user option, so the format is a plain #{?#{@icon},#{@icon},#{window_index}} lookup. It runs from run-shell -b on window-change hooks only, never on the render path. Deliberately NOT a #() job: those expand EMPTY while restarting, which is the bug that made status elements wink out in the first place. Reading a pre-set option means a slow or failed run leaves the previous icon in place instead of blanking the field. Hooks are limited to those tmux 3.7b actually has -- after-kill-window, after-move-window and after-swap-window do not exist and error on source. window-unlinked covers kills; session-window-changed catches move/swap, which have no hook of their own. --- .local/bin/tmux-window-icons | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100755 .local/bin/tmux-window-icons diff --git a/.local/bin/tmux-window-icons b/.local/bin/tmux-window-icons new file mode 100755 index 0000000..14d55b5 --- /dev/null +++ b/.local/bin/tmux-window-icons @@ -0,0 +1,24 @@ +#!/bin/bash +# Stamp every window with its circled-number icon as a WINDOW-level user option. +# +# Why not do this in the format string: the obvious approach nests ten +# #{?#{==:#{window_index},N},...} conditionals, which tmux re-evaluates for +# every window on every redraw -- and redraws are driven by PANE ACTIVITY, so a +# busy pane re-runs all ten per window, several times a second. +# +# Here the work happens ONCE per window change (driven by hooks) and the render +# path becomes #{@icon}: a plain variable lookup, no conditionals, no forks. +# +# Do NOT call this from the status format itself -- that would make it a #() +# job, which renders EMPTY while restarting and is the original bug. +symbols=(❶ ❷ ❸ ❹ ❺ ❻ ❼ ❽ ❾ ❿) +while IFS= read -r w; do + idx=${w##*:} + [[ $idx =~ ^[0-9]+$ ]] || continue + if (( idx >= 1 && idx <= ${#symbols[@]} )); then + icon=${symbols[idx-1]} + else + icon=$idx + fi + tmux set -w -t "$w" @icon "$icon" 2>/dev/null +done < <(tmux list-windows -a -F '#{session_name}:#{window_index}' 2>/dev/null)