Extend agent-nav.sh to also work with windows indices

This commit is contained in:
2026-09-14 16:02:33 +01:00
parent 9ce131462f
commit 4337db3aec
2 changed files with 57 additions and 22 deletions

View File

@@ -1,24 +1,27 @@
#!/usr/bin/env bash
# Cycle the agent popup through the windows of the session underneath it rather
# than through the agent session's own window list. Bound to <prefix>C-n and
# <prefix>C-p inside the agent session (see tmux.conf).
# Drive the agent popup from the windows of the session underneath it rather
# than from the agent session's own window list. Bound inside the agent session
# to <prefix>C-n and <prefix>C-p ("next"/"prev") and to <prefix>0..9 (a window
# index) -- see tmux.conf.
#
# Root windows with no agent window are stepped over; cycling never creates an
# agent window, only <prefix>A does. Both the client underneath and the popup
# move, so closing the popup leaves you on the window you cycled to.
# Root windows with no agent window are stepped over when cycling and do nothing
# when named by index, so the popup and the session below it never disagree.
# Neither ever creates an agent window; only <prefix>A does. Both the client
# underneath and the popup move, so closing the popup leaves you where you went.
set -e
direction=${1:-next}
target=${1:-next}
us=$'\x1f'
# Fall back to plain window cycling within the agent session when there is no
# usable client underneath (popup opened before this was configured, or the
# recorded client has gone away).
fallback() {
case $direction in
case $target in
next) tmux next-window -t agent ;;
prev) tmux previous-window -t agent ;;
*) tmux next-window -t agent ;;
*) tmux select-window -t "agent:$target" 2>/dev/null || true ;;
esac
exit 0
}
@@ -64,22 +67,48 @@ agent_window_for() {
return 1
}
# The underlying session's windows, in its own order, each with its first pane's
# directory. Panes are listed grouped by window in index order.
# Move the client underneath and the popup together. The agent window is re-keyed
# to the window being moved to, so the sync hook that the select below fires
# agrees with what happened here instead of dragging the client back to wherever
# that window was last opened from.
move_to() { # move_to <root window id> <agent window id>
tmux set-window-option -t "$2" @agent_origin_window "$1"
tmux select-window -t "$1"
tmux select-window -t "$2"
exit 0
}
# The underlying session's windows, in its own order, each with its index and its
# first pane's directory. Panes are listed grouped by window in index order.
root_windows=()
root_indexes=()
root_paths=()
seen_window=""
while IFS=$us read -r window_id start_path current_path; do
while IFS=$us read -r window_id window_index start_path current_path; do
[ "$window_id" != "$seen_window" ] || continue
seen_window=$window_id
root_windows+=("$window_id")
root_indexes+=("$window_index")
root_paths+=("$start_path$us$current_path")
done < <(tmux list-panes -s -t "$client_session" \
-F "#{window_id}$us#{pane_start_path}$us#{pane_current_path}" 2>/dev/null || true)
-F "#{window_id}$us#{window_index}$us#{pane_start_path}$us#{pane_current_path}" \
2>/dev/null || true)
count=${#root_windows[@]}
[ "$count" -gt 0 ] || exit 0
# <prefix>0..9: the window with that index in the session underneath. Doing
# nothing when it has no agent window keeps the popup and that session in step.
if [ -n "$target" ] && [ -z "${target//[0-9]/}" ]; then
for i in "${!root_windows[@]}"; do
[ "${root_indexes[$i]}" = "$target" ] || continue
IFS=$us read -r start_path current_path <<<"${root_paths[$i]}"
target_agent=$(agent_window_for "${root_windows[$i]}" "$start_path" "$current_path") || exit 0
move_to "${root_windows[$i]}" "$target_agent"
done
exit 0
fi
# Where the client sits in that order.
current=0
for i in "${!root_windows[@]}"; do
@@ -89,7 +118,7 @@ for i in "${!root_windows[@]}"; do
fi
done
if [ "$direction" = prev ]; then step=-1; else step=1; fi
if [ "$target" = prev ]; then step=-1; else step=1; fi
# Walk in that direction for the first window with an agent window, wrapping.
# Landing back on the starting window means nothing else has one: selecting it
@@ -100,12 +129,5 @@ for _ in $(seq 1 "$count"); do
target_root=${root_windows[$i]}
IFS=$us read -r start_path current_path <<<"${root_paths[$i]}"
target_agent=$(agent_window_for "$target_root" "$start_path" "$current_path") || continue
# Re-key the agent window to the window being moved to, so the sync hook that
# the select below fires agrees with what happened here instead of dragging
# the client back to wherever that window was last opened from.
tmux set-window-option -t "$target_agent" @agent_origin_window "$target_root"
tmux select-window -t "$target_root"
tmux select-window -t "$target_agent"
exit 0
move_to "$target_root" "$target_agent"
done

View File

@@ -112,6 +112,19 @@ bind C-p if -F '#{==:#{session_name},agent}' {
previous-window
}
# Numbered window selection. Inside the agent popup the number addresses the
# underlying session's windows, doing nothing if that window has no agent window.
bind 0 if -F '#{==:#{session_name},agent}' "run -b '~/.config/tmux/agent-nav.sh 0'" 'select-window -t :=0'
bind 1 if -F '#{==:#{session_name},agent}' "run -b '~/.config/tmux/agent-nav.sh 1'" 'select-window -t :=1'
bind 2 if -F '#{==:#{session_name},agent}' "run -b '~/.config/tmux/agent-nav.sh 2'" 'select-window -t :=2'
bind 3 if -F '#{==:#{session_name},agent}' "run -b '~/.config/tmux/agent-nav.sh 3'" 'select-window -t :=3'
bind 4 if -F '#{==:#{session_name},agent}' "run -b '~/.config/tmux/agent-nav.sh 4'" 'select-window -t :=4'
bind 5 if -F '#{==:#{session_name},agent}' "run -b '~/.config/tmux/agent-nav.sh 5'" 'select-window -t :=5'
bind 6 if -F '#{==:#{session_name},agent}' "run -b '~/.config/tmux/agent-nav.sh 6'" 'select-window -t :=6'
bind 7 if -F '#{==:#{session_name},agent}' "run -b '~/.config/tmux/agent-nav.sh 7'" 'select-window -t :=7'
bind 8 if -F '#{==:#{session_name},agent}' "run -b '~/.config/tmux/agent-nav.sh 8'" 'select-window -t :=8'
bind 9 if -F '#{==:#{session_name},agent}' "run -b '~/.config/tmux/agent-nav.sh 9'" 'select-window -t :=9'
# Kill current window
bind W kill-window